forked from arduino/Arduino
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathPImage.java
2862 lines (2502 loc) · 93.1 KB
/
PImage.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
/*
Part of the Processing project - http://processing.org
Copyright (c) 2004-08 Ben Fry and Casey Reas
Copyright (c) 2001-04 Massachusetts Institute of Technology
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General
Public License along with this library; if not, write to the
Free Software Foundation, Inc., 59 Temple Place, Suite 330,
Boston, MA 02111-1307 USA
*/
package processing.core;
import java.awt.image.*;
import java.io.*;
import java.util.HashMap;
import javax.imageio.ImageIO;
/**
* Datatype for storing images. Processing can display <b>.gif</b>, <b>.jpg</b>, <b>.tga</b>, and <b>.png</b> images. Images may be displayed in 2D and 3D space.
* Before an image is used, it must be loaded with the <b>loadImage()</b> function.
* The <b>PImage</b> object contains fields for the <b>width</b> and <b>height</b> of the image,
* as well as an array called <b>pixels[]</b> which contains the values for every pixel in the image.
* A group of methods, described below, allow easy access to the image's pixels and alpha channel and simplify the process of compositing.
* <br><br>Before using the <b>pixels[]</b> array, be sure to use the <b>loadPixels()</b> method on the image to make sure that the pixel data is properly loaded.
* <br><br>To create a new image, use the <b>createImage()</b> function (do not use <b>new PImage()</b>).
* =advanced
*
* Storage class for pixel data. This is the base class for most image and
* pixel information, such as PGraphics and the video library classes.
* <P>
* Code for copying, resizing, scaling, and blending contributed
* by <A HREF="http://www.toxi.co.uk">toxi</A>.
* <P>
*
* @webref image
* @usage Web & Application
* @instanceName img any variable of type PImage
* @see processing.core.PApplet#loadImage(String)
* @see processing.core.PApplet#imageMode(int)
* @see processing.core.PApplet#createImage(int, int)
*/
public class PImage implements PConstants, Cloneable {
/**
* Format for this image, one of RGB, ARGB or ALPHA.
* note that RGB images still require 0xff in the high byte
* because of how they'll be manipulated by other functions
*/
public int format;
/**
* Array containing the values for all the pixels in the image. These values are of the color datatype.
* This array is the size of the image, meaning if the image is 100x100 pixels, there will be 10000 values
* and if the window is 200x300 pixels, there will be 60000 values.
* The <b>index</b> value defines the position of a value within the array.
* For example, the statement <b>color b = img.pixels[230]</b> will set the variable <b>b</b> equal to the value at that location in the array.
* Before accessing this array, the data must loaded with the <b>loadPixels()</b> method.
* After the array data has been modified, the <b>updatePixels()</b> method must be run to update the changes.
* Without <b>loadPixels()</b>, running the code may (or will in future releases) result in a NullPointerException.
* @webref
* @brief Array containing the color of every pixel in the image
*/
public int[] pixels;
/**
* The width of the image in units of pixels.
* @webref
* @brief Image width
*/
public int width;
/**
* The height of the image in units of pixels.
* @webref
* @brief Image height
*/
public int height;
/**
* Path to parent object that will be used with save().
* This prevents users from needing savePath() to use PImage.save().
*/
public PApplet parent;
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
/** for subclasses that need to store info about the image */
protected HashMap<Object,Object> cacheMap;
/** modified portion of the image */
protected boolean modified;
protected int mx1, my1, mx2, my2;
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
// private fields
private int fracU, ifU, fracV, ifV, u1, u2, v1, v2, sX, sY, iw, iw1, ih1;
private int ul, ll, ur, lr, cUL, cLL, cUR, cLR;
private int srcXOffset, srcYOffset;
private int r, g, b, a;
private int[] srcBuffer;
// fixed point precision is limited to 15 bits!!
static final int PRECISIONB = 15;
static final int PRECISIONF = 1 << PRECISIONB;
static final int PREC_MAXVAL = PRECISIONF-1;
static final int PREC_ALPHA_SHIFT = 24-PRECISIONB;
static final int PREC_RED_SHIFT = 16-PRECISIONB;
// internal kernel stuff for the gaussian blur filter
private int blurRadius;
private int blurKernelSize;
private int[] blurKernel;
private int[][] blurMult;
//////////////////////////////////////////////////////////////
/**
* Create an empty image object, set its format to RGB.
* The pixel array is not allocated.
*/
public PImage() {
format = ARGB; // default to ARGB images for release 0116
// cache = null;
}
/**
* Create a new RGB (alpha ignored) image of a specific size.
* All pixels are set to zero, meaning black, but since the
* alpha is zero, it will be transparent.
*/
public PImage(int width, int height) {
init(width, height, RGB);
// toxi: is it maybe better to init the image with max alpha enabled?
//for(int i=0; i<pixels.length; i++) pixels[i]=0xffffffff;
// fry: i'm opting for the full transparent image, which is how
// photoshop works, and our audience oughta be familiar with.
// also, i want to avoid having to set all those pixels since
// in java it's super slow, and most using this fxn will be
// setting all the pixels anyway.
// toxi: agreed and same reasons why i left it out ;)
}
/**
*
* @param width image width
* @param height image height
* @param format Either RGB, ARGB, ALPHA (grayscale alpha channel)
*/
public PImage(int width, int height, int format) {
init(width, height, format);
}
/**
* Function to be used by subclasses of PImage to init later than
* at the constructor, or re-init later when things changes.
* Used by Capture and Movie classes (and perhaps others),
* because the width/height will not be known when super() is called.
* (Leave this public so that other libraries can do the same.)
*/
public void init(int width, int height, int format) { // ignore
this.width = width;
this.height = height;
this.pixels = new int[width*height];
this.format = format;
// this.cache = null;
}
/**
* Check the alpha on an image, using a really primitive loop.
*/
protected void checkAlpha() {
if (pixels == null) return;
for (int i = 0; i < pixels.length; i++) {
// since transparency is often at corners, hopefully this
// will find a non-transparent pixel quickly and exit
if ((pixels[i] & 0xff000000) != 0xff000000) {
format = ARGB;
break;
}
}
}
//////////////////////////////////////////////////////////////
/**
* Construct a new PImage from a java.awt.Image. This constructor assumes
* that you've done the work of making sure a MediaTracker has been used
* to fully download the data and that the img is valid.
*
* @param img assumes a MediaTracker has been used to fully download the data and the img is valid
*/
public PImage(java.awt.Image img) {
if (img instanceof BufferedImage) {
BufferedImage bi = (BufferedImage) img;
width = bi.getWidth();
height = bi.getHeight();
pixels = new int[width * height];
WritableRaster raster = bi.getRaster();
raster.getDataElements(0, 0, width, height, pixels);
} else { // go the old school java 1.0 route
// System.out.println(img.getClass().getName());
width = img.getWidth(null);
height = img.getHeight(null);
pixels = new int[width * height];
PixelGrabber pg =
new PixelGrabber(img, 0, 0, width, height, pixels, 0, width);
try {
pg.grabPixels();
} catch (InterruptedException e) { }
}
format = RGB;
// cache = null;
}
/**
* Returns a BufferedImage from this PImage.
*/
public java.awt.Image getImage() {
loadPixels();
int type = (format == RGB) ?
BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB;
BufferedImage image = new BufferedImage(width, height, type);
WritableRaster wr = image.getRaster();
wr.setDataElements(0, 0, width, height, pixels);
return image;
}
//////////////////////////////////////////////////////////////
/**
* Store data of some kind for a renderer that requires extra metadata of
* some kind. Usually this is a renderer-specific representation of the
* image data, for instance a BufferedImage with tint() settings applied for
* PGraphicsJava2D, or resized image data and OpenGL texture indices for
* PGraphicsOpenGL.
*/
public void setCache(Object parent, Object storage) {
if (cacheMap == null) cacheMap = new HashMap<Object, Object>();
cacheMap.put(parent, storage);
}
/**
* Get cache storage data for the specified renderer. Because each renderer
* will cache data in different formats, it's necessary to store cache data
* keyed by the renderer object. Otherwise, attempting to draw the same
* image to both a PGraphicsJava2D and a PGraphicsOpenGL will cause errors.
* @param parent The PGraphics object (or any object, really) associated
* @return data stored for the specified parent
*/
public Object getCache(Object parent) {
if (cacheMap == null) return null;
return cacheMap.get(parent);
}
/**
* Remove information associated with this renderer from the cache, if any.
* @param parent The PGraphics object whose cache data should be removed
*/
public void removeCache(Object parent) {
if (cacheMap != null) {
cacheMap.remove(parent);
}
}
//////////////////////////////////////////////////////////////
// MARKING IMAGE AS MODIFIED / FOR USE w/ GET/SET
public boolean isModified() { // ignore
return modified;
}
public void setModified() { // ignore
modified = true;
}
public void setModified(boolean m) { // ignore
modified = m;
}
/**
* Loads the pixel data for the image into its <b>pixels[]</b> array. This function must always be called before reading from or writing to <b>pixels[]</b>.
* <br><br>Certain renderers may or may not seem to require <b>loadPixels()</b> or <b>updatePixels()</b>. However, the rule is that any time you want to manipulate the <b>pixels[]</b> array, you must first call <b>loadPixels()</b>, and after changes have been made, call <b>updatePixels()</b>. Even if the renderer may not seem to use this function in the current Processing release, this will always be subject to change.
* =advanced
* Call this when you want to mess with the pixels[] array.
* <p/>
* For subclasses where the pixels[] buffer isn't set by default,
* this should copy all data into the pixels[] array
*
* @webref
* @brief Loads the pixel data for the image into its pixels[] array
*/
public void loadPixels() { // ignore
}
public void updatePixels() { // ignore
updatePixelsImpl(0, 0, width, height);
}
/**
* Updates the image with the data in its <b>pixels[]</b> array. Use in conjunction with <b>loadPixels()</b>. If you're only reading pixels from the array, there's no need to call <b>updatePixels()</b>.
* <br><br>Certain renderers may or may not seem to require <b>loadPixels()</b> or <b>updatePixels()</b>. However, the rule is that any time you want to manipulate the <b>pixels[]</b> array, you must first call <b>loadPixels()</b>, and after changes have been made, call <b>updatePixels()</b>. Even if the renderer may not seem to use this function in the current Processing release, this will always be subject to change.
* <br><br>Currently, none of the renderers use the additional parameters to <b>updatePixels()</b>, however this may be implemented in the future.
* =advanced
* Mark the pixels in this region as needing an update.
* This is not currently used by any of the renderers, however the api
* is structured this way in the hope of being able to use this to
* speed things up in the future.
* @webref
* @brief Updates the image with the data in its pixels[] array
* @param x
* @param y
* @param w
* @param h
*/
public void updatePixels(int x, int y, int w, int h) { // ignore
// if (imageMode == CORNER) { // x2, y2 are w/h
// x2 += x1;
// y2 += y1;
//
// } else if (imageMode == CENTER) {
// x1 -= x2 / 2;
// y1 -= y2 / 2;
// x2 += x1;
// y2 += y1;
// }
updatePixelsImpl(x, y, w, h);
}
protected void updatePixelsImpl(int x, int y, int w, int h) {
int x2 = x + w;
int y2 = y + h;
if (!modified) {
mx1 = x;
mx2 = x2;
my1 = y;
my2 = y2;
modified = true;
} else {
if (x < mx1) mx1 = x;
if (x > mx2) mx2 = x;
if (y < my1) my1 = y;
if (y > my2) my2 = y;
if (x2 < mx1) mx1 = x2;
if (x2 > mx2) mx2 = x2;
if (y2 < my1) my1 = y2;
if (y2 > my2) my2 = y2;
}
}
//////////////////////////////////////////////////////////////
// COPYING IMAGE DATA
/**
* Duplicate an image, returns new PImage object.
* The pixels[] array for the new object will be unique
* and recopied from the source image. This is implemented as an
* override of Object.clone(). We recommend using get() instead,
* because it prevents you from needing to catch the
* CloneNotSupportedException, and from doing a cast from the result.
*/
public Object clone() throws CloneNotSupportedException { // ignore
PImage c = (PImage) super.clone();
// super.clone() will only copy the reference to the pixels
// array, so this will do a proper duplication of it instead.
c.pixels = new int[width * height];
System.arraycopy(pixels, 0, c.pixels, 0, pixels.length);
// return the goods
return c;
}
/**
* Resize the image to a new width and height. To make the image scale proportionally, use 0 as the value for the <b>wide</b> or <b>high</b> parameter.
*
* @webref
* @brief Changes the size of an image to a new width and height
* @param wide the resized image width
* @param high the resized image height
*
* @see processing.core.PImage#get(int, int, int, int)
*/
public void resize(int wide, int high) { // ignore
// Make sure that the pixels[] array is valid
loadPixels();
if (wide <= 0 && high <= 0) {
width = 0; // Gimme a break, don't waste my time
height = 0;
pixels = new int[0];
} else {
if (wide == 0) { // Use height to determine relative size
float diff = (float) high / (float) height;
wide = (int) (width * diff);
} else if (high == 0) { // Use the width to determine relative size
float diff = (float) wide / (float) width;
high = (int) (height * diff);
}
PImage temp = new PImage(wide, high, this.format);
temp.copy(this, 0, 0, width, height, 0, 0, wide, high);
this.width = wide;
this.height = high;
this.pixels = temp.pixels;
}
// Mark the pixels array as altered
updatePixels();
}
//////////////////////////////////////////////////////////////
// GET/SET PIXELS
/**
* Returns an ARGB "color" type (a packed 32 bit int with the color.
* If the coordinate is outside the image, zero is returned
* (black, but completely transparent).
* <P>
* If the image is in RGB format (i.e. on a PVideo object),
* the value will get its high bits set, just to avoid cases where
* they haven't been set already.
* <P>
* If the image is in ALPHA format, this returns a white with its
* alpha value set.
* <P>
* This function is included primarily for beginners. It is quite
* slow because it has to check to see if the x, y that was provided
* is inside the bounds, and then has to check to see what image
* type it is. If you want things to be more efficient, access the
* pixels[] array directly.
*/
public int get(int x, int y) {
if ((x < 0) || (y < 0) || (x >= width) || (y >= height)) return 0;
switch (format) {
case RGB:
return pixels[y*width + x] | 0xff000000;
case ARGB:
return pixels[y*width + x];
case ALPHA:
return (pixels[y*width + x] << 24) | 0xffffff;
}
return 0;
}
/**
* Reads the color of any pixel or grabs a group of pixels. If no parameters are specified, the entire image is returned. Get the value of one pixel by specifying an x,y coordinate. Get a section of the display window by specifing an additional <b>width</b> and <b>height</b> parameter. If the pixel requested is outside of the image window, black is returned. The numbers returned are scaled according to the current color ranges, but only RGB values are returned by this function. Even though you may have drawn a shape with <b>colorMode(HSB)</b>, the numbers returned will be in RGB.
* <br><br>Getting the color of a single pixel with <b>get(x, y)</b> is easy, but not as fast as grabbing the data directly from <b>pixels[]</b>. The equivalent statement to "get(x, y)" using <b>pixels[]</b> is "pixels[y*width+x]". Processing requires calling <b>loadPixels()</b> to load the display window data into the <b>pixels[]</b> array before getting the values.
* <br><br>As of release 0149, this function ignores <b>imageMode()</b>.
*
* @webref
* @brief Reads the color of any pixel or grabs a rectangle of pixels
* @param x x-coordinate of the pixel
* @param y y-coordinate of the pixel
* @param w width of pixel rectangle to get
* @param h height of pixel rectangle to get
*
* @see processing.core.PImage#set(int, int, int)
* @see processing.core.PImage#pixels
* @see processing.core.PImage#copy(PImage, int, int, int, int, int, int, int, int)
*/
public PImage get(int x, int y, int w, int h) {
/*
if (imageMode == CORNERS) { // if CORNER, do nothing
//x2 += x1; y2 += y1;
// w/h are x2/y2 in this case, bring em down to size
w = (w - x);
h = (h - y);
} else if (imageMode == CENTER) {
x -= w/2;
y -= h/2;
}
*/
if (x < 0) {
w += x; // clip off the left edge
x = 0;
}
if (y < 0) {
h += y; // clip off some of the height
y = 0;
}
if (x + w > width) w = width - x;
if (y + h > height) h = height - y;
return getImpl(x, y, w, h);
}
/**
* Internal function to actually handle getting a block of pixels that
* has already been properly cropped to a valid region. That is, x/y/w/h
* are guaranteed to be inside the image space, so the implementation can
* use the fastest possible pixel copying method.
*/
protected PImage getImpl(int x, int y, int w, int h) {
PImage newbie = new PImage(w, h, format);
newbie.parent = parent;
int index = y*width + x;
int index2 = 0;
for (int row = y; row < y+h; row++) {
System.arraycopy(pixels, index, newbie.pixels, index2, w);
index += width;
index2 += w;
}
return newbie;
}
/**
* Returns a copy of this PImage. Equivalent to get(0, 0, width, height).
*/
public PImage get() {
try {
PImage clone = (PImage) clone();
// don't want to pass this down to the others
// http://dev.processing.org/bugs/show_bug.cgi?id=1245
clone.cacheMap = null;
return clone;
} catch (CloneNotSupportedException e) {
return null;
}
}
/**
* Changes the color of any pixel or writes an image directly into the image. The <b>x</b> and <b>y</b> parameter specify the pixel or the upper-left corner of the image. The <b>color</b> parameter specifies the color value.<br><br>Setting the color of a single pixel with <b>set(x, y)</b> is easy, but not as fast as putting the data directly into <b>pixels[]</b>. The equivalent statement to "set(x, y, #000000)" using <b>pixels[]</b> is "pixels[y*width+x] = #000000". Processing requires calling <b>loadPixels()</b> to load the display window data into the <b>pixels[]</b> array before getting the values and calling <b>updatePixels()</b> to update the window.
* <br><br>As of release 0149, this function ignores <b>imageMode()</b>.
*
* @webref
* @brief Writes a color to any pixel or writes an image into another
* @param x x-coordinate of the pixel or upper-left corner of the image
* @param y y-coordinate of the pixel or upper-left corner of the image
* @param c any value of the color datatype
*
* @see processing.core.PImage#get(int, int, int, int)
* @see processing.core.PImage#pixels
* @see processing.core.PImage#copy(PImage, int, int, int, int, int, int, int, int)
*/
public void set(int x, int y, int c) {
if ((x < 0) || (y < 0) || (x >= width) || (y >= height)) return;
pixels[y*width + x] = c;
updatePixelsImpl(x, y, x+1, y+1); // slow?
}
/**
* Efficient method of drawing an image's pixels directly to this surface.
* No variations are employed, meaning that any scale, tint, or imageMode
* settings will be ignored.
*/
public void set(int x, int y, PImage src) {
int sx = 0;
int sy = 0;
int sw = src.width;
int sh = src.height;
// if (imageMode == CENTER) {
// x -= src.width/2;
// y -= src.height/2;
// }
if (x < 0) { // off left edge
sx -= x;
sw += x;
x = 0;
}
if (y < 0) { // off top edge
sy -= y;
sh += y;
y = 0;
}
if (x + sw > width) { // off right edge
sw = width - x;
}
if (y + sh > height) { // off bottom edge
sh = height - y;
}
// this could be nonexistant
if ((sw <= 0) || (sh <= 0)) return;
setImpl(x, y, sx, sy, sw, sh, src);
}
/**
* Internal function to actually handle setting a block of pixels that
* has already been properly cropped from the image to a valid region.
*/
protected void setImpl(int dx, int dy, int sx, int sy, int sw, int sh,
PImage src) {
int srcOffset = sy * src.width + sx;
int dstOffset = dy * width + dx;
for (int y = sy; y < sy + sh; y++) {
System.arraycopy(src.pixels, srcOffset, pixels, dstOffset, sw);
srcOffset += src.width;
dstOffset += width;
}
updatePixelsImpl(sx, sy, sx+sw, sy+sh);
}
//////////////////////////////////////////////////////////////
// ALPHA CHANNEL
/**
* Set alpha channel for an image. Black colors in the source
* image will make the destination image completely transparent,
* and white will make things fully opaque. Gray values will
* be in-between steps.
* <P>
* Strictly speaking the "blue" value from the source image is
* used as the alpha color. For a fully grayscale image, this
* is correct, but for a color image it's not 100% accurate.
* For a more accurate conversion, first use filter(GRAY)
* which will make the image into a "correct" grayscale by
* performing a proper luminance-based conversion.
*
* @param maskArray any array of Integer numbers used as the alpha channel, needs to be same length as the image's pixel array
*/
public void mask(int maskArray[]) {
loadPixels();
// don't execute if mask image is different size
if (maskArray.length != pixels.length) {
throw new RuntimeException("The PImage used with mask() must be " +
"the same size as the applet.");
}
for (int i = 0; i < pixels.length; i++) {
pixels[i] = ((maskArray[i] & 0xff) << 24) | (pixels[i] & 0xffffff);
}
format = ARGB;
updatePixels();
}
/**
* Masks part of an image from displaying by loading another image and using it as an alpha channel.
* This mask image should only contain grayscale data, but only the blue color channel is used.
* The mask image needs to be the same size as the image to which it is applied.
* In addition to using a mask image, an integer array containing the alpha channel data can be specified directly.
* This method is useful for creating dynamically generated alpha masks.
* This array must be of the same length as the target image's pixels array and should contain only grayscale data of values between 0-255.
* @webref
* @brief Masks part of the image from displaying
* @param maskImg any PImage object used as the alpha channel for "img", needs to be same size as "img"
*/
public void mask(PImage maskImg) {
mask(maskImg.pixels);
}
//////////////////////////////////////////////////////////////
// IMAGE FILTERS
public void filter(int kind) {
loadPixels();
switch (kind) {
case BLUR:
// TODO write basic low-pass filter blur here
// what does photoshop do on the edges with this guy?
// better yet.. why bother? just use gaussian with radius 1
filter(BLUR, 1);
break;
case GRAY:
if (format == ALPHA) {
// for an alpha image, convert it to an opaque grayscale
for (int i = 0; i < pixels.length; i++) {
int col = 255 - pixels[i];
pixels[i] = 0xff000000 | (col << 16) | (col << 8) | col;
}
format = RGB;
} else {
// Converts RGB image data into grayscale using
// weighted RGB components, and keeps alpha channel intact.
// [toxi 040115]
for (int i = 0; i < pixels.length; i++) {
int col = pixels[i];
// luminance = 0.3*red + 0.59*green + 0.11*blue
// 0.30 * 256 = 77
// 0.59 * 256 = 151
// 0.11 * 256 = 28
int lum = (77*(col>>16&0xff) + 151*(col>>8&0xff) + 28*(col&0xff))>>8;
pixels[i] = (col & ALPHA_MASK) | lum<<16 | lum<<8 | lum;
}
}
break;
case INVERT:
for (int i = 0; i < pixels.length; i++) {
//pixels[i] = 0xff000000 |
pixels[i] ^= 0xffffff;
}
break;
case POSTERIZE:
throw new RuntimeException("Use filter(POSTERIZE, int levels) " +
"instead of filter(POSTERIZE)");
case OPAQUE:
for (int i = 0; i < pixels.length; i++) {
pixels[i] |= 0xff000000;
}
format = RGB;
break;
case THRESHOLD:
filter(THRESHOLD, 0.5f);
break;
// [toxi20050728] added new filters
case ERODE:
dilate(true);
break;
case DILATE:
dilate(false);
break;
}
updatePixels(); // mark as modified
}
/**
* Filters an image as defined by one of the following modes:<br><br>THRESHOLD - converts the image to black and white pixels depending if they are above or below the threshold defined by the level parameter. The level must be between 0.0 (black) and 1.0(white). If no level is specified, 0.5 is used.<br><br>GRAY - converts any colors in the image to grayscale equivalents<br><br>INVERT - sets each pixel to its inverse value<br><br>POSTERIZE - limits each channel of the image to the number of colors specified as the level parameter<br><br>BLUR - executes a Guassian blur with the level parameter specifying the extent of the blurring. If no level parameter is used, the blur is equivalent to Guassian blur of radius 1.<br><br>OPAQUE - sets the alpha channel to entirely opaque.<br><br>ERODE - reduces the light areas with the amount defined by the level parameter.<br><br>DILATE - increases the light areas with the amount defined by the level parameter
* =advanced
* Method to apply a variety of basic filters to this image.
* <P>
* <UL>
* <LI>filter(BLUR) provides a basic blur.
* <LI>filter(GRAY) converts the image to grayscale based on luminance.
* <LI>filter(INVERT) will invert the color components in the image.
* <LI>filter(OPAQUE) set all the high bits in the image to opaque
* <LI>filter(THRESHOLD) converts the image to black and white.
* <LI>filter(DILATE) grow white/light areas
* <LI>filter(ERODE) shrink white/light areas
* </UL>
* Luminance conversion code contributed by
* <A HREF="http://www.toxi.co.uk">toxi</A>
* <P/>
* Gaussian blur code contributed by
* <A HREF="http://incubator.quasimondo.com">Mario Klingemann</A>
*
* @webref
* @brief Converts the image to grayscale or black and white
* @param kind Either THRESHOLD, GRAY, INVERT, POSTERIZE, BLUR, OPAQUE, ERODE, or DILATE
* @param param in the range from 0 to 1
*/
public void filter(int kind, float param) {
loadPixels();
switch (kind) {
case BLUR:
if (format == ALPHA)
blurAlpha(param);
else if (format == ARGB)
blurARGB(param);
else
blurRGB(param);
break;
case GRAY:
throw new RuntimeException("Use filter(GRAY) instead of " +
"filter(GRAY, param)");
case INVERT:
throw new RuntimeException("Use filter(INVERT) instead of " +
"filter(INVERT, param)");
case OPAQUE:
throw new RuntimeException("Use filter(OPAQUE) instead of " +
"filter(OPAQUE, param)");
case POSTERIZE:
int levels = (int)param;
if ((levels < 2) || (levels > 255)) {
throw new RuntimeException("Levels must be between 2 and 255 for " +
"filter(POSTERIZE, levels)");
}
int levels1 = levels - 1;
for (int i = 0; i < pixels.length; i++) {
int rlevel = (pixels[i] >> 16) & 0xff;
int glevel = (pixels[i] >> 8) & 0xff;
int blevel = pixels[i] & 0xff;
rlevel = (((rlevel * levels) >> 8) * 255) / levels1;
glevel = (((glevel * levels) >> 8) * 255) / levels1;
blevel = (((blevel * levels) >> 8) * 255) / levels1;
pixels[i] = ((0xff000000 & pixels[i]) |
(rlevel << 16) |
(glevel << 8) |
blevel);
}
break;
case THRESHOLD: // greater than or equal to the threshold
int thresh = (int) (param * 255);
for (int i = 0; i < pixels.length; i++) {
int max = Math.max((pixels[i] & RED_MASK) >> 16,
Math.max((pixels[i] & GREEN_MASK) >> 8,
(pixels[i] & BLUE_MASK)));
pixels[i] = (pixels[i] & ALPHA_MASK) |
((max < thresh) ? 0x000000 : 0xffffff);
}
break;
// [toxi20050728] added new filters
case ERODE:
throw new RuntimeException("Use filter(ERODE) instead of " +
"filter(ERODE, param)");
case DILATE:
throw new RuntimeException("Use filter(DILATE) instead of " +
"filter(DILATE, param)");
}
updatePixels(); // mark as modified
}
/**
* Optimized code for building the blur kernel.
* further optimized blur code (approx. 15% for radius=20)
* bigger speed gains for larger radii (~30%)
* added support for various image types (ALPHA, RGB, ARGB)
* [toxi 050728]
*/
protected void buildBlurKernel(float r) {
int radius = (int) (r * 3.5f);
radius = (radius < 1) ? 1 : ((radius < 248) ? radius : 248);
if (blurRadius != radius) {
blurRadius = radius;
blurKernelSize = 1 + blurRadius<<1;
blurKernel = new int[blurKernelSize];
blurMult = new int[blurKernelSize][256];
int bk,bki;
int[] bm,bmi;
for (int i = 1, radiusi = radius - 1; i < radius; i++) {
blurKernel[radius+i] = blurKernel[radiusi] = bki = radiusi * radiusi;
bm=blurMult[radius+i];
bmi=blurMult[radiusi--];
for (int j = 0; j < 256; j++)
bm[j] = bmi[j] = bki*j;
}
bk = blurKernel[radius] = radius * radius;
bm = blurMult[radius];
for (int j = 0; j < 256; j++)
bm[j] = bk*j;
}
}
protected void blurAlpha(float r) {
int sum, cb;
int read, ri, ym, ymi, bk0;
int b2[] = new int[pixels.length];
int yi = 0;
buildBlurKernel(r);
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
//cb = cg = cr = sum = 0;
cb = sum = 0;
read = x - blurRadius;
if (read<0) {
bk0=-read;
read=0;
} else {
if (read >= width)
break;
bk0=0;
}
for (int i = bk0; i < blurKernelSize; i++) {
if (read >= width)
break;
int c = pixels[read + yi];
int[] bm=blurMult[i];
cb += bm[c & BLUE_MASK];
sum += blurKernel[i];
read++;
}
ri = yi + x;
b2[ri] = cb / sum;
}
yi += width;
}
yi = 0;
ym=-blurRadius;
ymi=ym*width;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
//cb = cg = cr = sum = 0;
cb = sum = 0;
if (ym<0) {
bk0 = ri = -ym;
read = x;
} else {
if (ym >= height)
break;
bk0 = 0;
ri = ym;
read = x + ymi;
}
for (int i = bk0; i < blurKernelSize; i++) {
if (ri >= height)
break;
int[] bm=blurMult[i];
cb += bm[b2[read]];
sum += blurKernel[i];
ri++;
read += width;
}
pixels[x+yi] = (cb/sum);
}
yi += width;
ymi += width;
ym++;
}
}
protected void blurRGB(float r) {
int sum, cr, cg, cb; //, k;
int /*pixel,*/ read, ri, /*roff,*/ ym, ymi, /*riw,*/ bk0;
int r2[] = new int[pixels.length];
int g2[] = new int[pixels.length];
int b2[] = new int[pixels.length];
int yi = 0;