propane 2.7.2-java → 2.8.0.pre-java
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.travis.yml +10 -0
- data/CHANGELOG.md +1 -1
- data/README.md +10 -8
- data/Rakefile +1 -1
- data/lib/propane/app.rb +3 -3
- data/lib/propane/version.rb +1 -1
- data/lib/{processing-core.jar → propane-2.8.0.jar} +0 -0
- data/library/control_panel/control_panel.rb +3 -2
- data/pom.rb +89 -88
- data/pom.xml +75 -46
- data/propane.gemspec +1 -2
- data/src/main/java/japplemenubar/JAppleMenuBar.java +88 -0
- data/src/main/java/japplemenubar/libjAppleMenuBar.jnilib +0 -0
- data/src/{monkstone → main/java/monkstone}/ColorUtil.java +0 -0
- data/src/{monkstone → main/java/monkstone}/MathToolModule.java +0 -0
- data/src/{monkstone → main/java/monkstone}/PropaneLibrary.java +0 -0
- data/src/{monkstone → main/java/monkstone}/core/LibraryProxy.java +0 -0
- data/src/{monkstone → main/java/monkstone}/fastmath/Deglut.java +0 -0
- data/src/{monkstone → main/java/monkstone}/fastmath/package-info.java +0 -0
- data/src/{monkstone → main/java/monkstone}/filechooser/Chooser.java +0 -0
- data/src/{monkstone → main/java/monkstone}/noise/SimplexNoise.java +0 -0
- data/src/{monkstone → main/java/monkstone}/slider/CustomHorizontalSlider.java +0 -0
- data/src/{monkstone → main/java/monkstone}/slider/CustomVerticalSlider.java +0 -0
- data/src/{monkstone → main/java/monkstone}/slider/SimpleHorizontalSlider.java +0 -0
- data/src/{monkstone → main/java/monkstone}/slider/SimpleSlider.java +0 -0
- data/src/{monkstone → main/java/monkstone}/slider/SimpleVerticalSlider.java +0 -0
- data/src/{monkstone → main/java/monkstone}/slider/Slider.java +0 -0
- data/src/{monkstone → main/java/monkstone}/slider/SliderBar.java +0 -0
- data/src/{monkstone → main/java/monkstone}/slider/SliderGroup.java +0 -0
- data/src/{monkstone → main/java/monkstone}/slider/WheelHandler.java +0 -0
- data/src/{monkstone → main/java/monkstone}/vecmath/AppRender.java +0 -0
- data/src/{monkstone → main/java/monkstone}/vecmath/JRender.java +0 -0
- data/src/{monkstone → main/java/monkstone}/vecmath/ShapeRender.java +0 -0
- data/src/{monkstone → main/java/monkstone}/vecmath/package-info.java +0 -0
- data/src/{monkstone → main/java/monkstone}/vecmath/vec2/Vec2.java +0 -0
- data/src/{monkstone → main/java/monkstone}/vecmath/vec2/package-info.java +0 -0
- data/src/{monkstone → main/java/monkstone}/vecmath/vec3/Vec3.java +0 -0
- data/src/{monkstone → main/java/monkstone}/vecmath/vec3/package-info.java +0 -0
- data/src/{monkstone → main/java/monkstone}/videoevent/VideoInterface.java +0 -0
- data/src/{monkstone → main/java/monkstone}/videoevent/package-info.java +0 -0
- data/src/main/java/processing/awt/PGraphicsJava2D.java +3029 -0
- data/src/main/java/processing/awt/PShapeJava2D.java +377 -0
- data/src/main/java/processing/awt/PSurfaceAWT.java +1567 -0
- data/src/main/java/processing/core/PApplet.java +15709 -0
- data/src/main/java/processing/core/PConstants.java +527 -0
- data/src/main/java/processing/core/PFont.java +1098 -0
- data/src/main/java/processing/core/PGraphics.java +8467 -0
- data/src/main/java/processing/core/PImage.java +3438 -0
- data/src/main/java/processing/core/PMatrix.java +208 -0
- data/src/main/java/processing/core/PMatrix2D.java +534 -0
- data/src/main/java/processing/core/PMatrix3D.java +877 -0
- data/src/main/java/processing/core/PShape.java +3445 -0
- data/src/main/java/processing/core/PShapeOBJ.java +469 -0
- data/src/main/java/processing/core/PShapeSVG.java +1787 -0
- data/src/main/java/processing/core/PStyle.java +63 -0
- data/src/main/java/processing/core/PSurface.java +161 -0
- data/src/main/java/processing/core/PSurfaceNone.java +374 -0
- data/src/main/java/processing/core/PVector.java +1063 -0
- data/src/main/java/processing/data/FloatDict.java +829 -0
- data/src/main/java/processing/data/FloatList.java +912 -0
- data/src/main/java/processing/data/IntDict.java +796 -0
- data/src/main/java/processing/data/IntList.java +913 -0
- data/src/main/java/processing/data/JSONArray.java +1260 -0
- data/src/main/java/processing/data/JSONObject.java +2282 -0
- data/src/main/java/processing/data/JSONTokener.java +435 -0
- data/src/main/java/processing/data/Sort.java +46 -0
- data/src/main/java/processing/data/StringDict.java +601 -0
- data/src/main/java/processing/data/StringList.java +775 -0
- data/src/main/java/processing/data/Table.java +4923 -0
- data/src/main/java/processing/data/TableRow.java +198 -0
- data/src/main/java/processing/data/XML.java +1149 -0
- data/src/main/java/processing/event/Event.java +125 -0
- data/src/main/java/processing/event/KeyEvent.java +70 -0
- data/src/main/java/processing/event/MouseEvent.java +149 -0
- data/src/main/java/processing/event/TouchEvent.java +57 -0
- data/src/main/java/processing/opengl/FontTexture.java +379 -0
- data/src/main/java/processing/opengl/FrameBuffer.java +503 -0
- data/src/main/java/processing/opengl/LinePath.java +623 -0
- data/src/main/java/processing/opengl/LineStroker.java +685 -0
- data/src/main/java/processing/opengl/PGL.java +3366 -0
- data/src/main/java/processing/opengl/PGraphics2D.java +615 -0
- data/src/main/java/processing/opengl/PGraphics3D.java +281 -0
- data/src/main/java/processing/opengl/PGraphicsOpenGL.java +13634 -0
- data/src/main/java/processing/opengl/PJOGL.java +1966 -0
- data/src/main/java/processing/opengl/PShader.java +1478 -0
- data/src/main/java/processing/opengl/PShapeOpenGL.java +5234 -0
- data/src/main/java/processing/opengl/PSurfaceJOGL.java +1315 -0
- data/src/main/java/processing/opengl/Texture.java +1670 -0
- data/src/main/java/processing/opengl/VertexBuffer.java +88 -0
- data/src/main/java/processing/opengl/cursors/arrow.png +0 -0
- data/src/main/java/processing/opengl/cursors/cross.png +0 -0
- data/src/main/java/processing/opengl/cursors/hand.png +0 -0
- data/src/main/java/processing/opengl/cursors/license.txt +27 -0
- data/src/main/java/processing/opengl/cursors/move.png +0 -0
- data/src/main/java/processing/opengl/cursors/text.png +0 -0
- data/src/main/java/processing/opengl/cursors/wait.png +0 -0
- data/src/main/java/processing/opengl/shaders/ColorFrag.glsl +32 -0
- data/src/main/java/processing/opengl/shaders/ColorVert.glsl +34 -0
- data/src/main/java/processing/opengl/shaders/LightFrag.glsl +33 -0
- data/src/main/java/processing/opengl/shaders/LightVert-vc4.glsl +154 -0
- data/src/main/java/processing/opengl/shaders/LightVert.glsl +151 -0
- data/src/main/java/processing/opengl/shaders/LineFrag.glsl +32 -0
- data/src/main/java/processing/opengl/shaders/LineVert.glsl +100 -0
- data/src/main/java/processing/opengl/shaders/MaskFrag.glsl +40 -0
- data/src/main/java/processing/opengl/shaders/PointFrag.glsl +32 -0
- data/src/main/java/processing/opengl/shaders/PointVert.glsl +56 -0
- data/src/main/java/processing/opengl/shaders/TexFrag.glsl +37 -0
- data/src/main/java/processing/opengl/shaders/TexLightFrag.glsl +37 -0
- data/src/main/java/processing/opengl/shaders/TexLightVert-vc4.glsl +160 -0
- data/src/main/java/processing/opengl/shaders/TexLightVert.glsl +157 -0
- data/src/main/java/processing/opengl/shaders/TexVert.glsl +38 -0
- data/src/main/resources/icon/icon-1024.png +0 -0
- data/src/main/resources/icon/icon-128.png +0 -0
- data/src/main/resources/icon/icon-16.png +0 -0
- data/src/main/resources/icon/icon-256.png +0 -0
- data/src/main/resources/icon/icon-32.png +0 -0
- data/src/main/resources/icon/icon-48.png +0 -0
- data/src/main/resources/icon/icon-512.png +0 -0
- data/src/main/resources/icon/icon-64.png +0 -0
- data/src/main/resources/license.txt +508 -0
- data/vendors/Rakefile +5 -20
- metadata +115 -33
- data/lib/propane.jar +0 -0
@@ -0,0 +1,3438 @@
|
|
1
|
+
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
|
2
|
+
|
3
|
+
/*
|
4
|
+
Part of the Processing project - http://processing.org
|
5
|
+
|
6
|
+
Copyright (c) 2004-14 Ben Fry and Casey Reas
|
7
|
+
Copyright (c) 2001-04 Massachusetts Institute of Technology
|
8
|
+
|
9
|
+
This library is free software; you can redistribute it and/or
|
10
|
+
modify it under the terms of the GNU Lesser General Public
|
11
|
+
License as published by the Free Software Foundation; either
|
12
|
+
version 2.1 of the License, or (at your option) any later version.
|
13
|
+
|
14
|
+
This library is distributed in the hope that it will be useful,
|
15
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
16
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
17
|
+
Lesser General Public License for more details.
|
18
|
+
|
19
|
+
You should have received a copy of the GNU Lesser General
|
20
|
+
Public License along with this library; if not, write to the
|
21
|
+
Free Software Foundation, Inc., 59 Temple Place, Suite 330,
|
22
|
+
Boston, MA 02111-1307 USA
|
23
|
+
*/
|
24
|
+
|
25
|
+
package processing.core;
|
26
|
+
|
27
|
+
import java.awt.*;
|
28
|
+
import java.awt.image.*;
|
29
|
+
import java.io.*;
|
30
|
+
import java.util.Iterator;
|
31
|
+
|
32
|
+
import javax.imageio.*;
|
33
|
+
import javax.imageio.metadata.*;
|
34
|
+
|
35
|
+
|
36
|
+
/**
|
37
|
+
* ( begin auto-generated from PImage.xml )
|
38
|
+
*
|
39
|
+
* Datatype for storing images. Processing can display <b>.gif</b>,
|
40
|
+
* <b>.jpg</b>, <b>.tga</b>, and <b>.png</b> images. Images may be
|
41
|
+
* displayed in 2D and 3D space. Before an image is used, it must be loaded
|
42
|
+
* with the <b>loadImage()</b> function. The <b>PImage</b> class contains
|
43
|
+
* fields for the <b>width</b> and <b>height</b> of the image, as well as
|
44
|
+
* an array called <b>pixels[]</b> that contains the values for every pixel
|
45
|
+
* in the image. The methods described below allow easy access to the
|
46
|
+
* image's pixels and alpha channel and simplify the process of compositing.<br/>
|
47
|
+
* <br/> using the <b>pixels[]</b> array, be sure to use the
|
48
|
+
* <b>loadPixels()</b> method on the image to make sure that the pixel data
|
49
|
+
* is properly loaded.<br/>
|
50
|
+
* <br/> create a new image, use the <b>createImage()</b> function. Do not
|
51
|
+
* use the syntax <b>new PImage()</b>.
|
52
|
+
*
|
53
|
+
* ( end auto-generated )
|
54
|
+
*
|
55
|
+
* @webref image
|
56
|
+
* @usage Web & Application
|
57
|
+
* @instanceName pimg any object of type PImage
|
58
|
+
* @see PApplet#loadImage(String)
|
59
|
+
* @see PApplet#imageMode(int)
|
60
|
+
* @see PApplet#createImage(int, int, int)
|
61
|
+
*/
|
62
|
+
public class PImage implements PConstants, Cloneable {
|
63
|
+
|
64
|
+
/**
|
65
|
+
* Format for this image, one of RGB, ARGB or ALPHA.
|
66
|
+
* note that RGB images still require 0xff in the high byte
|
67
|
+
* because of how they'll be manipulated by other functions
|
68
|
+
*/
|
69
|
+
public int format;
|
70
|
+
|
71
|
+
/**
|
72
|
+
* ( begin auto-generated from pixels.xml )
|
73
|
+
*
|
74
|
+
* Array containing the values for all the pixels in the display window.
|
75
|
+
* These values are of the color datatype. This array is the size of the
|
76
|
+
* display window. For example, if the image is 100x100 pixels, there will
|
77
|
+
* be 10000 values and if the window is 200x300 pixels, there will be 60000
|
78
|
+
* values. The <b>index</b> value defines the position of a value within
|
79
|
+
* the array. For example, the statement <b>color b = pixels[230]</b> will
|
80
|
+
* set the variable <b>b</b> to be equal to the value at that location in
|
81
|
+
* the array.<br />
|
82
|
+
* <br />
|
83
|
+
* Before accessing this array, the data must loaded with the
|
84
|
+
* <b>loadPixels()</b> function. After the array data has been modified,
|
85
|
+
* the <b>updatePixels()</b> function must be run to update the changes.
|
86
|
+
* Without <b>loadPixels()</b>, running the code may (or will in future
|
87
|
+
* releases) result in a NullPointerException.
|
88
|
+
*
|
89
|
+
* ( end auto-generated )
|
90
|
+
*
|
91
|
+
* @webref image:pixels
|
92
|
+
* @usage web_application
|
93
|
+
* @brief Array containing the color of every pixel in the image
|
94
|
+
*/
|
95
|
+
public int[] pixels;
|
96
|
+
|
97
|
+
/** 1 for most images, 2 for hi-dpi/retina */
|
98
|
+
public int pixelDensity = 1;
|
99
|
+
|
100
|
+
/** Actual dimensions of pixels array, taking into account the 2x setting. */
|
101
|
+
public int pixelWidth;
|
102
|
+
public int pixelHeight;
|
103
|
+
|
104
|
+
/**
|
105
|
+
* ( begin auto-generated from PImage_width.xml )
|
106
|
+
*
|
107
|
+
* The width of the image in units of pixels.
|
108
|
+
*
|
109
|
+
* ( end auto-generated )
|
110
|
+
* @webref pimage:field
|
111
|
+
* @usage web_application
|
112
|
+
* @brief Image width
|
113
|
+
*/
|
114
|
+
public int width;
|
115
|
+
|
116
|
+
/**
|
117
|
+
* ( begin auto-generated from PImage_height.xml )
|
118
|
+
*
|
119
|
+
* The height of the image in units of pixels.
|
120
|
+
*
|
121
|
+
* ( end auto-generated )
|
122
|
+
* @webref pimage:field
|
123
|
+
* @usage web_application
|
124
|
+
* @brief Image height
|
125
|
+
*/
|
126
|
+
public int height;
|
127
|
+
|
128
|
+
/**
|
129
|
+
* Path to parent object that will be used with save().
|
130
|
+
* This prevents users from needing savePath() to use PImage.save().
|
131
|
+
*/
|
132
|
+
public PApplet parent;
|
133
|
+
|
134
|
+
|
135
|
+
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
|
136
|
+
|
137
|
+
|
138
|
+
/** modified portion of the image */
|
139
|
+
protected boolean modified;
|
140
|
+
protected int mx1, my1, mx2, my2;
|
141
|
+
|
142
|
+
/** Loaded pixels flag */
|
143
|
+
public boolean loaded = false;
|
144
|
+
|
145
|
+
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
|
146
|
+
|
147
|
+
|
148
|
+
// private fields
|
149
|
+
private int fracU, ifU, fracV, ifV, u1, u2, v1, v2, sX, sY, iw, iw1, ih1;
|
150
|
+
private int ul, ll, ur, lr, cUL, cLL, cUR, cLR;
|
151
|
+
private int srcXOffset, srcYOffset;
|
152
|
+
private int r, g, b, a;
|
153
|
+
private int[] srcBuffer;
|
154
|
+
|
155
|
+
// fixed point precision is limited to 15 bits!!
|
156
|
+
static final int PRECISIONB = 15;
|
157
|
+
static final int PRECISIONF = 1 << PRECISIONB;
|
158
|
+
static final int PREC_MAXVAL = PRECISIONF-1;
|
159
|
+
static final int PREC_ALPHA_SHIFT = 24-PRECISIONB;
|
160
|
+
static final int PREC_RED_SHIFT = 16-PRECISIONB;
|
161
|
+
|
162
|
+
// internal kernel stuff for the gaussian blur filter
|
163
|
+
private int blurRadius;
|
164
|
+
private int blurKernelSize;
|
165
|
+
private int[] blurKernel;
|
166
|
+
private int[][] blurMult;
|
167
|
+
|
168
|
+
// colour component bitmasks (moved from PConstants in 2.0b7)
|
169
|
+
public static final int ALPHA_MASK = 0xff000000;
|
170
|
+
public static final int RED_MASK = 0x00ff0000;
|
171
|
+
public static final int GREEN_MASK = 0x0000ff00;
|
172
|
+
public static final int BLUE_MASK = 0x000000ff;
|
173
|
+
|
174
|
+
|
175
|
+
//////////////////////////////////////////////////////////////
|
176
|
+
|
177
|
+
|
178
|
+
/**
|
179
|
+
* ( begin auto-generated from PImage.xml )
|
180
|
+
*
|
181
|
+
* Datatype for storing images. Processing can display <b>.gif</b>,
|
182
|
+
* <b>.jpg</b>, <b>.tga</b>, and <b>.png</b> images. Images may be
|
183
|
+
* displayed in 2D and 3D space. Before an image is used, it must be loaded
|
184
|
+
* with the <b>loadImage()</b> function. The <b>PImage</b> object contains
|
185
|
+
* fields for the <b>width</b> and <b>height</b> of the image, as well as
|
186
|
+
* an array called <b>pixels[]</b> which contains the values for every
|
187
|
+
* pixel in the image. A group of methods, described below, allow easy
|
188
|
+
* access to the image's pixels and alpha channel and simplify the process
|
189
|
+
* of compositing.
|
190
|
+
* <br/> <br/>
|
191
|
+
* Before using the <b>pixels[]</b> array, be sure to use the
|
192
|
+
* <b>loadPixels()</b> method on the image to make sure that the pixel data
|
193
|
+
* is properly loaded.
|
194
|
+
* <br/> <br/>
|
195
|
+
* To create a new image, use the <b>createImage()</b> function (do not use
|
196
|
+
* <b>new PImage()</b>).
|
197
|
+
* ( end auto-generated )
|
198
|
+
* @nowebref
|
199
|
+
* @usage web_application
|
200
|
+
* @see PApplet#loadImage(String, String)
|
201
|
+
* @see PApplet#imageMode(int)
|
202
|
+
* @see PApplet#createImage(int, int, int)
|
203
|
+
*/
|
204
|
+
public PImage() {
|
205
|
+
format = ARGB; // default to ARGB images for release 0116
|
206
|
+
pixelDensity = 1;
|
207
|
+
}
|
208
|
+
|
209
|
+
|
210
|
+
/**
|
211
|
+
* @nowebref
|
212
|
+
* @param width image width
|
213
|
+
* @param height image height
|
214
|
+
*/
|
215
|
+
public PImage(int width, int height) {
|
216
|
+
init(width, height, RGB, 1);
|
217
|
+
|
218
|
+
// toxi: is it maybe better to init the image with max alpha enabled?
|
219
|
+
//for(int i=0; i<pixels.length; i++) pixels[i]=0xffffffff;
|
220
|
+
// fry: i'm opting for the full transparent image, which is how
|
221
|
+
// photoshop works, and our audience oughta be familiar with.
|
222
|
+
// also, i want to avoid having to set all those pixels since
|
223
|
+
// in java it's super slow, and most using this fxn will be
|
224
|
+
// setting all the pixels anyway.
|
225
|
+
// toxi: agreed and same reasons why i left it out ;)
|
226
|
+
}
|
227
|
+
|
228
|
+
|
229
|
+
/**
|
230
|
+
* @nowebref
|
231
|
+
* @param format Either RGB, ARGB, ALPHA (grayscale alpha channel)
|
232
|
+
*/
|
233
|
+
public PImage(int width, int height, int format) {
|
234
|
+
init(width, height, format, 1);
|
235
|
+
}
|
236
|
+
|
237
|
+
|
238
|
+
public PImage(int width, int height, int format, int factor) {
|
239
|
+
init(width, height, format, factor);
|
240
|
+
}
|
241
|
+
|
242
|
+
|
243
|
+
/**
|
244
|
+
* Do not remove, see notes in the other variant.
|
245
|
+
*/
|
246
|
+
public void init(int width, int height, int format) { // ignore
|
247
|
+
init(width, height, format, 1);
|
248
|
+
}
|
249
|
+
|
250
|
+
|
251
|
+
/**
|
252
|
+
* Function to be used by subclasses of PImage to init later than
|
253
|
+
* at the constructor, or re-init later when things changes.
|
254
|
+
* Used by Capture and Movie classes (and perhaps others),
|
255
|
+
* because the width/height will not be known when super() is called.
|
256
|
+
* (Leave this public so that other libraries can do the same.)
|
257
|
+
*/
|
258
|
+
public void init(int width, int height, int format, int factor) { // ignore
|
259
|
+
this.width = width;
|
260
|
+
this.height = height;
|
261
|
+
this.format = format;
|
262
|
+
this.pixelDensity = factor;
|
263
|
+
|
264
|
+
pixelWidth = width * pixelDensity;
|
265
|
+
pixelHeight = height * pixelDensity;
|
266
|
+
this.pixels = new int[pixelWidth * pixelHeight];
|
267
|
+
}
|
268
|
+
|
269
|
+
|
270
|
+
/**
|
271
|
+
* Check the alpha on an image, using a really primitive loop.
|
272
|
+
*/
|
273
|
+
protected void checkAlpha() {
|
274
|
+
if (pixels == null) return;
|
275
|
+
|
276
|
+
for (int i = 0; i < pixels.length; i++) {
|
277
|
+
// since transparency is often at corners, hopefully this
|
278
|
+
// will find a non-transparent pixel quickly and exit
|
279
|
+
if ((pixels[i] & 0xff000000) != 0xff000000) {
|
280
|
+
format = ARGB;
|
281
|
+
break;
|
282
|
+
}
|
283
|
+
}
|
284
|
+
}
|
285
|
+
|
286
|
+
|
287
|
+
//////////////////////////////////////////////////////////////
|
288
|
+
|
289
|
+
|
290
|
+
/**
|
291
|
+
* Construct a new PImage from a java.awt.Image. This constructor assumes
|
292
|
+
* that you've done the work of making sure a MediaTracker has been used
|
293
|
+
* to fully download the data and that the img is valid.
|
294
|
+
*
|
295
|
+
* @nowebref
|
296
|
+
* @param img assumes a MediaTracker has been used to fully download
|
297
|
+
* the data and the img is valid
|
298
|
+
*/
|
299
|
+
public PImage(Image img) {
|
300
|
+
format = RGB;
|
301
|
+
if (img instanceof BufferedImage) {
|
302
|
+
BufferedImage bi = (BufferedImage) img;
|
303
|
+
width = bi.getWidth();
|
304
|
+
height = bi.getHeight();
|
305
|
+
int type = bi.getType();
|
306
|
+
if (type == BufferedImage.TYPE_3BYTE_BGR ||
|
307
|
+
type == BufferedImage.TYPE_4BYTE_ABGR) {
|
308
|
+
pixels = new int[width * height];
|
309
|
+
bi.getRGB(0, 0, width, height, pixels, 0, width);
|
310
|
+
if (type == BufferedImage.TYPE_4BYTE_ABGR) {
|
311
|
+
format = ARGB;
|
312
|
+
} else {
|
313
|
+
opaque();
|
314
|
+
}
|
315
|
+
} else {
|
316
|
+
DataBuffer db = bi.getRaster().getDataBuffer();
|
317
|
+
if (db instanceof DataBufferInt) {
|
318
|
+
pixels = ((DataBufferInt) db).getData();
|
319
|
+
if (type == BufferedImage.TYPE_INT_ARGB) {
|
320
|
+
format = ARGB;
|
321
|
+
} else if (type == BufferedImage.TYPE_INT_RGB) {
|
322
|
+
opaque();
|
323
|
+
}
|
324
|
+
}
|
325
|
+
}
|
326
|
+
}
|
327
|
+
// Implements fall-through if not DataBufferInt above, or not a
|
328
|
+
// known type, or not DataBufferInt for the data itself.
|
329
|
+
if (pixels == null) { // go the old school Java 1.0 route
|
330
|
+
width = img.getWidth(null);
|
331
|
+
height = img.getHeight(null);
|
332
|
+
pixels = new int[width * height];
|
333
|
+
PixelGrabber pg =
|
334
|
+
new PixelGrabber(img, 0, 0, width, height, pixels, 0, width);
|
335
|
+
try {
|
336
|
+
pg.grabPixels();
|
337
|
+
} catch (InterruptedException e) { }
|
338
|
+
}
|
339
|
+
pixelDensity = 1;
|
340
|
+
pixelWidth = width;
|
341
|
+
pixelHeight = height;
|
342
|
+
}
|
343
|
+
|
344
|
+
|
345
|
+
/**
|
346
|
+
* Use the getNative() method instead, which allows library interfaces to be
|
347
|
+
* written in a cross-platform fashion for desktop, Android, and others.
|
348
|
+
* This is still included for PGraphics objects, which may need the image.
|
349
|
+
*/
|
350
|
+
public Image getImage() { // ignore
|
351
|
+
return (Image) getNative();
|
352
|
+
}
|
353
|
+
|
354
|
+
|
355
|
+
/**
|
356
|
+
* Returns a native BufferedImage from this PImage.
|
357
|
+
*/
|
358
|
+
public Object getNative() { // ignore
|
359
|
+
loadPixels();
|
360
|
+
int type = (format == RGB) ?
|
361
|
+
BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB;
|
362
|
+
BufferedImage image = new BufferedImage(pixelWidth, pixelHeight, type);
|
363
|
+
WritableRaster wr = image.getRaster();
|
364
|
+
wr.setDataElements(0, 0, pixelWidth, pixelHeight, pixels);
|
365
|
+
return image;
|
366
|
+
}
|
367
|
+
|
368
|
+
|
369
|
+
//////////////////////////////////////////////////////////////
|
370
|
+
|
371
|
+
// MARKING IMAGE AS MODIFIED / FOR USE w/ GET/SET
|
372
|
+
|
373
|
+
|
374
|
+
public boolean isModified() { // ignore
|
375
|
+
return modified;
|
376
|
+
}
|
377
|
+
|
378
|
+
|
379
|
+
public void setModified() { // ignore
|
380
|
+
modified = true;
|
381
|
+
mx1 = 0;
|
382
|
+
my1 = 0;
|
383
|
+
mx2 = pixelWidth;
|
384
|
+
my2 = pixelHeight;
|
385
|
+
}
|
386
|
+
|
387
|
+
|
388
|
+
public void setModified(boolean m) { // ignore
|
389
|
+
modified = m;
|
390
|
+
}
|
391
|
+
|
392
|
+
|
393
|
+
public int getModifiedX1() { // ignore
|
394
|
+
return mx1;
|
395
|
+
}
|
396
|
+
|
397
|
+
|
398
|
+
public int getModifiedX2() { // ignore
|
399
|
+
return mx2;
|
400
|
+
}
|
401
|
+
|
402
|
+
|
403
|
+
public int getModifiedY1() { // ignore
|
404
|
+
return my1;
|
405
|
+
}
|
406
|
+
|
407
|
+
|
408
|
+
public int getModifiedY2() { // ignore
|
409
|
+
return my2;
|
410
|
+
}
|
411
|
+
|
412
|
+
|
413
|
+
/**
|
414
|
+
* ( begin auto-generated from PImage_loadPixels.xml )
|
415
|
+
*
|
416
|
+
* Loads the pixel data for the image into its <b>pixels[]</b> array. This
|
417
|
+
* function must always be called before reading from or writing to <b>pixels[]</b>.
|
418
|
+
* <br/><br/> renderers may or may not seem to require <b>loadPixels()</b>
|
419
|
+
* or <b>updatePixels()</b>. However, the rule is that any time you want to
|
420
|
+
* manipulate the <b>pixels[]</b> array, you must first call
|
421
|
+
* <b>loadPixels()</b>, and after changes have been made, call
|
422
|
+
* <b>updatePixels()</b>. Even if the renderer may not seem to use this
|
423
|
+
* function in the current Processing release, this will always be subject
|
424
|
+
* to change.
|
425
|
+
*
|
426
|
+
* ( end auto-generated )
|
427
|
+
*
|
428
|
+
* <h3>Advanced</h3>
|
429
|
+
* Call this when you want to mess with the pixels[] array.
|
430
|
+
* <p/>
|
431
|
+
* For subclasses where the pixels[] buffer isn't set by default,
|
432
|
+
* this should copy all data into the pixels[] array
|
433
|
+
*
|
434
|
+
* @webref pimage:pixels
|
435
|
+
* @brief Loads the pixel data for the image into its pixels[] array
|
436
|
+
* @usage web_application
|
437
|
+
*/
|
438
|
+
public void loadPixels() { // ignore
|
439
|
+
if (pixels == null || pixels.length != pixelWidth*pixelHeight) {
|
440
|
+
pixels = new int[pixelWidth*pixelHeight];
|
441
|
+
}
|
442
|
+
setLoaded();
|
443
|
+
}
|
444
|
+
|
445
|
+
|
446
|
+
public void updatePixels() { // ignore
|
447
|
+
updatePixels(0, 0, pixelWidth, pixelHeight);
|
448
|
+
}
|
449
|
+
|
450
|
+
|
451
|
+
/**
|
452
|
+
* ( begin auto-generated from PImage_updatePixels.xml )
|
453
|
+
*
|
454
|
+
* Updates the image with the data in its <b>pixels[]</b> array. Use in
|
455
|
+
* conjunction with <b>loadPixels()</b>. If you're only reading pixels from
|
456
|
+
* the array, there's no need to call <b>updatePixels()</b>.
|
457
|
+
* <br/><br/> renderers may or may not seem to require <b>loadPixels()</b>
|
458
|
+
* or <b>updatePixels()</b>. However, the rule is that any time you want to
|
459
|
+
* manipulate the <b>pixels[]</b> array, you must first call
|
460
|
+
* <b>loadPixels()</b>, and after changes have been made, call
|
461
|
+
* <b>updatePixels()</b>. Even if the renderer may not seem to use this
|
462
|
+
* function in the current Processing release, this will always be subject
|
463
|
+
* to change.
|
464
|
+
* <br/> <br/>
|
465
|
+
* Currently, none of the renderers use the additional parameters to
|
466
|
+
* <b>updatePixels()</b>, however this may be implemented in the future.
|
467
|
+
*
|
468
|
+
* ( end auto-generated )
|
469
|
+
* <h3>Advanced</h3>
|
470
|
+
* Mark the pixels in this region as needing an update.
|
471
|
+
* This is not currently used by any of the renderers, however the api
|
472
|
+
* is structured this way in the hope of being able to use this to
|
473
|
+
* speed things up in the future.
|
474
|
+
* @webref pimage:pixels
|
475
|
+
* @brief Updates the image with the data in its pixels[] array
|
476
|
+
* @usage web_application
|
477
|
+
* @param x x-coordinate of the upper-left corner
|
478
|
+
* @param y y-coordinate of the upper-left corner
|
479
|
+
* @param w width
|
480
|
+
* @param h height
|
481
|
+
*/
|
482
|
+
public void updatePixels(int x, int y, int w, int h) { // ignore
|
483
|
+
int x2 = x + w;
|
484
|
+
int y2 = y + h;
|
485
|
+
|
486
|
+
if (!modified) {
|
487
|
+
mx1 = PApplet.max(0, x);
|
488
|
+
mx2 = PApplet.min(pixelWidth, x2);
|
489
|
+
my1 = PApplet.max(0, y);
|
490
|
+
my2 = PApplet.min(pixelHeight, y2);
|
491
|
+
modified = true;
|
492
|
+
|
493
|
+
} else {
|
494
|
+
if (x < mx1) mx1 = PApplet.max(0, x);
|
495
|
+
if (x > mx2) mx2 = PApplet.min(pixelWidth, x);
|
496
|
+
if (y < my1) my1 = PApplet.max(0, y);
|
497
|
+
if (y > my2) my2 = PApplet.min(pixelHeight, y);
|
498
|
+
|
499
|
+
if (x2 < mx1) mx1 = PApplet.max(0, x2);
|
500
|
+
if (x2 > mx2) mx2 = PApplet.min(pixelWidth, x2);
|
501
|
+
if (y2 < my1) my1 = PApplet.max(0, y2);
|
502
|
+
if (y2 > my2) my2 = PApplet.min(pixelHeight, y2);
|
503
|
+
}
|
504
|
+
}
|
505
|
+
|
506
|
+
|
507
|
+
//////////////////////////////////////////////////////////////
|
508
|
+
|
509
|
+
// COPYING IMAGE DATA
|
510
|
+
|
511
|
+
|
512
|
+
/**
|
513
|
+
* Duplicate an image, returns new PImage object.
|
514
|
+
* The pixels[] array for the new object will be unique
|
515
|
+
* and recopied from the source image. This is implemented as an
|
516
|
+
* override of Object.clone(). We recommend using get() instead,
|
517
|
+
* because it prevents you from needing to catch the
|
518
|
+
* CloneNotSupportedException, and from doing a cast from the result.
|
519
|
+
*/
|
520
|
+
@Override
|
521
|
+
public Object clone() throws CloneNotSupportedException { // ignore
|
522
|
+
return get();
|
523
|
+
}
|
524
|
+
|
525
|
+
|
526
|
+
/**
|
527
|
+
* ( begin auto-generated from PImage_resize.xml )
|
528
|
+
*
|
529
|
+
* Resize the image to a new width and height. To make the image scale
|
530
|
+
* proportionally, use 0 as the value for the <b>wide</b> or <b>high</b>
|
531
|
+
* parameter. For instance, to make the width of an image 150 pixels, and
|
532
|
+
* change the height using the same proportion, use resize(150, 0).<br />
|
533
|
+
* <br />
|
534
|
+
* Even though a PGraphics is technically a PImage, it is not possible to
|
535
|
+
* rescale the image data found in a PGraphics. (It's simply not possible
|
536
|
+
* to do this consistently across renderers: technically infeasible with
|
537
|
+
* P3D, or what would it even do with PDF?) If you want to resize PGraphics
|
538
|
+
* content, first get a copy of its image data using the <b>get()</b>
|
539
|
+
* method, and call <b>resize()</b> on the PImage that is returned.
|
540
|
+
*
|
541
|
+
* ( end auto-generated )
|
542
|
+
* @webref pimage:method
|
543
|
+
* @brief Changes the size of an image to a new width and height
|
544
|
+
* @usage web_application
|
545
|
+
* @param w the resized image width
|
546
|
+
* @param h the resized image height
|
547
|
+
* @see PImage#get(int, int, int, int)
|
548
|
+
*/
|
549
|
+
public void resize(int w, int h) { // ignore
|
550
|
+
if (w <= 0 && h <= 0) {
|
551
|
+
throw new IllegalArgumentException("width or height must be > 0 for resize");
|
552
|
+
}
|
553
|
+
|
554
|
+
if (w == 0) { // Use height to determine relative size
|
555
|
+
float diff = (float) h / (float) height;
|
556
|
+
w = (int) (width * diff);
|
557
|
+
} else if (h == 0) { // Use the width to determine relative size
|
558
|
+
float diff = (float) w / (float) width;
|
559
|
+
h = (int) (height * diff);
|
560
|
+
}
|
561
|
+
|
562
|
+
BufferedImage img =
|
563
|
+
shrinkImage((BufferedImage) getNative(), w*pixelDensity, h*pixelDensity);
|
564
|
+
|
565
|
+
PImage temp = new PImage(img);
|
566
|
+
this.pixelWidth = temp.width;
|
567
|
+
this.pixelHeight = temp.height;
|
568
|
+
|
569
|
+
// Get the resized pixel array
|
570
|
+
this.pixels = temp.pixels;
|
571
|
+
|
572
|
+
this.width = pixelWidth / pixelDensity;
|
573
|
+
this.height = pixelHeight / pixelDensity;
|
574
|
+
|
575
|
+
// Mark the pixels array as altered
|
576
|
+
updatePixels();
|
577
|
+
}
|
578
|
+
|
579
|
+
|
580
|
+
// Adapted from getFasterScaledInstance() method from page 111 of
|
581
|
+
// "Filthy Rich Clients" by Chet Haase and Romain Guy
|
582
|
+
// Additional modifications and simplifications have been added,
|
583
|
+
// plus a fix to deal with an infinite loop if images are expanded.
|
584
|
+
// http://code.google.com/p/processing/issues/detail?id=1463
|
585
|
+
static private BufferedImage shrinkImage(BufferedImage img,
|
586
|
+
int targetWidth, int targetHeight) {
|
587
|
+
int type = (img.getTransparency() == Transparency.OPAQUE) ?
|
588
|
+
BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB;
|
589
|
+
BufferedImage outgoing = img;
|
590
|
+
BufferedImage scratchImage = null;
|
591
|
+
Graphics2D g2 = null;
|
592
|
+
int prevW = outgoing.getWidth();
|
593
|
+
int prevH = outgoing.getHeight();
|
594
|
+
boolean isTranslucent = img.getTransparency() != Transparency.OPAQUE;
|
595
|
+
|
596
|
+
// Use multi-step technique: start with original size, then scale down in
|
597
|
+
// multiple passes with drawImage() until the target size is reached
|
598
|
+
int w = img.getWidth();
|
599
|
+
int h = img.getHeight();
|
600
|
+
|
601
|
+
do {
|
602
|
+
if (w > targetWidth) {
|
603
|
+
w /= 2;
|
604
|
+
// if this is the last step, do the exact size
|
605
|
+
if (w < targetWidth) {
|
606
|
+
w = targetWidth;
|
607
|
+
}
|
608
|
+
} else if (targetWidth >= w) {
|
609
|
+
w = targetWidth;
|
610
|
+
}
|
611
|
+
if (h > targetHeight) {
|
612
|
+
h /= 2;
|
613
|
+
if (h < targetHeight) {
|
614
|
+
h = targetHeight;
|
615
|
+
}
|
616
|
+
} else if (targetHeight >= h) {
|
617
|
+
h = targetHeight;
|
618
|
+
}
|
619
|
+
if (scratchImage == null || isTranslucent) {
|
620
|
+
// Use a single scratch buffer for all iterations and then copy
|
621
|
+
// to the final, correctly-sized image before returning
|
622
|
+
scratchImage = new BufferedImage(w, h, type);
|
623
|
+
g2 = scratchImage.createGraphics();
|
624
|
+
}
|
625
|
+
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
|
626
|
+
RenderingHints.VALUE_INTERPOLATION_BILINEAR);
|
627
|
+
g2.drawImage(outgoing, 0, 0, w, h, 0, 0, prevW, prevH, null);
|
628
|
+
prevW = w;
|
629
|
+
prevH = h;
|
630
|
+
outgoing = scratchImage;
|
631
|
+
} while (w != targetWidth || h != targetHeight);
|
632
|
+
|
633
|
+
if (g2 != null) {
|
634
|
+
g2.dispose();
|
635
|
+
}
|
636
|
+
|
637
|
+
// If we used a scratch buffer that is larger than our target size,
|
638
|
+
// create an image of the right size and copy the results into it
|
639
|
+
if (targetWidth != outgoing.getWidth() ||
|
640
|
+
targetHeight != outgoing.getHeight()) {
|
641
|
+
scratchImage = new BufferedImage(targetWidth, targetHeight, type);
|
642
|
+
g2 = scratchImage.createGraphics();
|
643
|
+
g2.drawImage(outgoing, 0, 0, null);
|
644
|
+
g2.dispose();
|
645
|
+
outgoing = scratchImage;
|
646
|
+
}
|
647
|
+
return outgoing;
|
648
|
+
}
|
649
|
+
|
650
|
+
|
651
|
+
//////////////////////////////////////////////////////////////
|
652
|
+
|
653
|
+
// MARKING IMAGE AS LOADED / FOR USE IN RENDERERS
|
654
|
+
|
655
|
+
|
656
|
+
public boolean isLoaded() { // ignore
|
657
|
+
return loaded;
|
658
|
+
}
|
659
|
+
|
660
|
+
|
661
|
+
public void setLoaded() { // ignore
|
662
|
+
loaded = true;
|
663
|
+
}
|
664
|
+
|
665
|
+
|
666
|
+
public void setLoaded(boolean l) { // ignore
|
667
|
+
loaded = l;
|
668
|
+
}
|
669
|
+
|
670
|
+
|
671
|
+
//////////////////////////////////////////////////////////////
|
672
|
+
|
673
|
+
// GET/SET PIXELS
|
674
|
+
|
675
|
+
|
676
|
+
/**
|
677
|
+
* ( begin auto-generated from PImage_get.xml )
|
678
|
+
*
|
679
|
+
* Reads the color of any pixel or grabs a section of an image. If no
|
680
|
+
* parameters are specified, the entire image is returned. Use the <b>x</b>
|
681
|
+
* and <b>y</b> parameters to get the value of one pixel. Get a section of
|
682
|
+
* the display window by specifying an additional <b>width</b> and
|
683
|
+
* <b>height</b> parameter. When getting an image, the <b>x</b> and
|
684
|
+
* <b>y</b> parameters define the coordinates for the upper-left corner of
|
685
|
+
* the image, regardless of the current <b>imageMode()</b>.<br />
|
686
|
+
* <br />
|
687
|
+
* If the pixel requested is outside of the image window, black is
|
688
|
+
* returned. The numbers returned are scaled according to the current color
|
689
|
+
* ranges, but only RGB values are returned by this function. For example,
|
690
|
+
* even though you may have drawn a shape with <b>colorMode(HSB)</b>, the
|
691
|
+
* numbers returned will be in RGB format.<br />
|
692
|
+
* <br />
|
693
|
+
* Getting the color of a single pixel with <b>get(x, y)</b> is easy, but
|
694
|
+
* not as fast as grabbing the data directly from <b>pixels[]</b>. The
|
695
|
+
* equivalent statement to <b>get(x, y)</b> using <b>pixels[]</b> is
|
696
|
+
* <b>pixels[y*width+x]</b>. See the reference for <b>pixels[]</b> for more information.
|
697
|
+
*
|
698
|
+
* ( end auto-generated )
|
699
|
+
*
|
700
|
+
* <h3>Advanced</h3>
|
701
|
+
* Returns an ARGB "color" type (a packed 32 bit int with the color.
|
702
|
+
* If the coordinate is outside the image, zero is returned
|
703
|
+
* (black, but completely transparent).
|
704
|
+
* <P>
|
705
|
+
* If the image is in RGB format (i.e. on a PVideo object),
|
706
|
+
* the value will get its high bits set, just to avoid cases where
|
707
|
+
* they haven't been set already.
|
708
|
+
* <P>
|
709
|
+
* If the image is in ALPHA format, this returns a white with its
|
710
|
+
* alpha value set.
|
711
|
+
* <P>
|
712
|
+
* This function is included primarily for beginners. It is quite
|
713
|
+
* slow because it has to check to see if the x, y that was provided
|
714
|
+
* is inside the bounds, and then has to check to see what image
|
715
|
+
* type it is. If you want things to be more efficient, access the
|
716
|
+
* pixels[] array directly.
|
717
|
+
*
|
718
|
+
* @webref image:pixels
|
719
|
+
* @brief Reads the color of any pixel or grabs a rectangle of pixels
|
720
|
+
* @usage web_application
|
721
|
+
* @param x x-coordinate of the pixel
|
722
|
+
* @param y y-coordinate of the pixel
|
723
|
+
* @see PApplet#set(int, int, int)
|
724
|
+
* @see PApplet#pixels
|
725
|
+
* @see PApplet#copy(PImage, int, int, int, int, int, int, int, int)
|
726
|
+
*/
|
727
|
+
public int get(int x, int y) {
|
728
|
+
if ((x < 0) || (y < 0) || (x >= pixelWidth) || (y >= pixelHeight)) return 0;
|
729
|
+
|
730
|
+
switch (format) {
|
731
|
+
case RGB:
|
732
|
+
return pixels[y*pixelWidth + x] | 0xff000000;
|
733
|
+
|
734
|
+
case ARGB:
|
735
|
+
return pixels[y*pixelWidth + x];
|
736
|
+
|
737
|
+
case ALPHA:
|
738
|
+
return (pixels[y*pixelWidth + x] << 24) | 0xffffff;
|
739
|
+
}
|
740
|
+
return 0;
|
741
|
+
}
|
742
|
+
|
743
|
+
|
744
|
+
/**
|
745
|
+
* @param w width of pixel rectangle to get
|
746
|
+
* @param h height of pixel rectangle to get
|
747
|
+
*/
|
748
|
+
public PImage get(int x, int y, int w, int h) {
|
749
|
+
int targetX = 0;
|
750
|
+
int targetY = 0;
|
751
|
+
int targetWidth = w;
|
752
|
+
int targetHeight = h;
|
753
|
+
boolean cropped = false;
|
754
|
+
|
755
|
+
if (x < 0) {
|
756
|
+
w += x; // x is negative, removes the left edge from the width
|
757
|
+
targetX = -x;
|
758
|
+
cropped = true;
|
759
|
+
x = 0;
|
760
|
+
}
|
761
|
+
if (y < 0) {
|
762
|
+
h += y; // y is negative, clip the number of rows
|
763
|
+
targetY = -y;
|
764
|
+
cropped = true;
|
765
|
+
y = 0;
|
766
|
+
}
|
767
|
+
|
768
|
+
if (x + w > pixelWidth) {
|
769
|
+
w = pixelWidth - x;
|
770
|
+
cropped = true;
|
771
|
+
}
|
772
|
+
if (y + h > pixelHeight) {
|
773
|
+
h = pixelHeight - y;
|
774
|
+
cropped = true;
|
775
|
+
}
|
776
|
+
|
777
|
+
if (w < 0) {
|
778
|
+
w = 0;
|
779
|
+
}
|
780
|
+
if (h < 0) {
|
781
|
+
h = 0;
|
782
|
+
}
|
783
|
+
|
784
|
+
int targetFormat = format;
|
785
|
+
if (cropped && format == RGB) {
|
786
|
+
targetFormat = ARGB;
|
787
|
+
}
|
788
|
+
|
789
|
+
PImage target = new PImage(targetWidth / pixelDensity,
|
790
|
+
targetHeight / pixelDensity,
|
791
|
+
targetFormat, pixelDensity);
|
792
|
+
target.parent = parent; // parent may be null so can't use createImage()
|
793
|
+
if (w > 0 && h > 0) {
|
794
|
+
getImpl(x, y, w, h, target, targetX, targetY);
|
795
|
+
}
|
796
|
+
return target;
|
797
|
+
}
|
798
|
+
|
799
|
+
|
800
|
+
/**
|
801
|
+
* Returns a copy of this PImage. Equivalent to get(0, 0, width, height).
|
802
|
+
* Deprecated, just use copy() instead.
|
803
|
+
*/
|
804
|
+
public PImage get() {
|
805
|
+
// Formerly this used clone(), which caused memory problems.
|
806
|
+
// http://code.google.com/p/processing/issues/detail?id=42
|
807
|
+
return get(0, 0, pixelWidth, pixelHeight);
|
808
|
+
}
|
809
|
+
|
810
|
+
|
811
|
+
public PImage copy() {
|
812
|
+
return get(0, 0, pixelWidth, pixelHeight);
|
813
|
+
}
|
814
|
+
|
815
|
+
|
816
|
+
/**
|
817
|
+
* Internal function to actually handle getting a block of pixels that
|
818
|
+
* has already been properly cropped to a valid region. That is, x/y/w/h
|
819
|
+
* are guaranteed to be inside the image space, so the implementation can
|
820
|
+
* use the fastest possible pixel copying method.
|
821
|
+
*/
|
822
|
+
protected void getImpl(int sourceX, int sourceY,
|
823
|
+
int sourceWidth, int sourceHeight,
|
824
|
+
PImage target, int targetX, int targetY) {
|
825
|
+
int sourceIndex = sourceY*pixelWidth + sourceX;
|
826
|
+
int targetIndex = targetY*target.pixelWidth + targetX;
|
827
|
+
for (int row = 0; row < sourceHeight; row++) {
|
828
|
+
System.arraycopy(pixels, sourceIndex, target.pixels, targetIndex, sourceWidth);
|
829
|
+
sourceIndex += pixelWidth;
|
830
|
+
targetIndex += target.pixelWidth;
|
831
|
+
}
|
832
|
+
}
|
833
|
+
|
834
|
+
|
835
|
+
/**
|
836
|
+
* ( begin auto-generated from PImage_set.xml )
|
837
|
+
*
|
838
|
+
* Changes the color of any pixel or writes an image directly into the
|
839
|
+
* display window.<br />
|
840
|
+
* <br />
|
841
|
+
* The <b>x</b> and <b>y</b> parameters specify the pixel to change and the
|
842
|
+
* <b>color</b> parameter specifies the color value. The color parameter is
|
843
|
+
* affected by the current color mode (the default is RGB values from 0 to
|
844
|
+
* 255). When setting an image, the <b>x</b> and <b>y</b> parameters define
|
845
|
+
* the coordinates for the upper-left corner of the image, regardless of
|
846
|
+
* the current <b>imageMode()</b>.
|
847
|
+
* <br /><br />
|
848
|
+
* Setting the color of a single pixel with <b>set(x, y)</b> is easy, but
|
849
|
+
* not as fast as putting the data directly into <b>pixels[]</b>. The
|
850
|
+
* equivalent statement to <b>set(x, y, #000000)</b> using <b>pixels[]</b>
|
851
|
+
* is <b>pixels[y*width+x] = #000000</b>. See the reference for
|
852
|
+
* <b>pixels[]</b> for more information.
|
853
|
+
*
|
854
|
+
* ( end auto-generated )
|
855
|
+
*
|
856
|
+
* @webref image:pixels
|
857
|
+
* @brief writes a color to any pixel or writes an image into another
|
858
|
+
* @usage web_application
|
859
|
+
* @param x x-coordinate of the pixel
|
860
|
+
* @param y y-coordinate of the pixel
|
861
|
+
* @param c any value of the color datatype
|
862
|
+
* @see PImage#get(int, int, int, int)
|
863
|
+
* @see PImage#pixels
|
864
|
+
* @see PImage#copy(PImage, int, int, int, int, int, int, int, int)
|
865
|
+
*/
|
866
|
+
public void set(int x, int y, int c) {
|
867
|
+
if ((x < 0) || (y < 0) || (x >= pixelWidth) || (y >= pixelHeight)) return;
|
868
|
+
pixels[y*pixelWidth + x] = c;
|
869
|
+
updatePixels(x, y, 1, 1); // slow...
|
870
|
+
}
|
871
|
+
|
872
|
+
|
873
|
+
/**
|
874
|
+
* <h3>Advanced</h3>
|
875
|
+
* Efficient method of drawing an image's pixels directly to this surface.
|
876
|
+
* No variations are employed, meaning that any scale, tint, or imageMode
|
877
|
+
* settings will be ignored.
|
878
|
+
*
|
879
|
+
* @param img image to copy into the original image
|
880
|
+
*/
|
881
|
+
public void set(int x, int y, PImage img) {
|
882
|
+
int sx = 0;
|
883
|
+
int sy = 0;
|
884
|
+
int sw = img.pixelWidth;
|
885
|
+
int sh = img.pixelHeight;
|
886
|
+
|
887
|
+
if (x < 0) { // off left edge
|
888
|
+
sx -= x;
|
889
|
+
sw += x;
|
890
|
+
x = 0;
|
891
|
+
}
|
892
|
+
if (y < 0) { // off top edge
|
893
|
+
sy -= y;
|
894
|
+
sh += y;
|
895
|
+
y = 0;
|
896
|
+
}
|
897
|
+
if (x + sw > pixelWidth) { // off right edge
|
898
|
+
sw = pixelWidth - x;
|
899
|
+
}
|
900
|
+
if (y + sh > pixelHeight) { // off bottom edge
|
901
|
+
sh = pixelHeight - y;
|
902
|
+
}
|
903
|
+
|
904
|
+
// this could be nonexistent
|
905
|
+
if ((sw <= 0) || (sh <= 0)) return;
|
906
|
+
|
907
|
+
setImpl(img, sx, sy, sw, sh, x, y);
|
908
|
+
}
|
909
|
+
|
910
|
+
|
911
|
+
/**
|
912
|
+
* Internal function to actually handle setting a block of pixels that
|
913
|
+
* has already been properly cropped from the image to a valid region.
|
914
|
+
*/
|
915
|
+
protected void setImpl(PImage sourceImage,
|
916
|
+
int sourceX, int sourceY,
|
917
|
+
int sourceWidth, int sourceHeight,
|
918
|
+
int targetX, int targetY) {
|
919
|
+
int sourceOffset = sourceY * sourceImage.pixelWidth + sourceX;
|
920
|
+
int targetOffset = targetY * pixelWidth + targetX;
|
921
|
+
|
922
|
+
for (int y = sourceY; y < sourceY + sourceHeight; y++) {
|
923
|
+
System.arraycopy(sourceImage.pixels, sourceOffset, pixels, targetOffset, sourceWidth);
|
924
|
+
sourceOffset += sourceImage.pixelWidth;
|
925
|
+
targetOffset += pixelWidth;
|
926
|
+
}
|
927
|
+
|
928
|
+
//updatePixelsImpl(targetX, targetY, sourceWidth, sourceHeight);
|
929
|
+
updatePixels(targetX, targetY, sourceWidth, sourceHeight);
|
930
|
+
}
|
931
|
+
|
932
|
+
|
933
|
+
|
934
|
+
//////////////////////////////////////////////////////////////
|
935
|
+
|
936
|
+
// ALPHA CHANNEL
|
937
|
+
|
938
|
+
|
939
|
+
/**
|
940
|
+
* @param maskArray array of integers used as the alpha channel, needs to be
|
941
|
+
* the same length as the image's pixel array.
|
942
|
+
*/
|
943
|
+
public void mask(int maskArray[]) { // ignore
|
944
|
+
loadPixels();
|
945
|
+
// don't execute if mask image is different size
|
946
|
+
if (maskArray.length != pixels.length) {
|
947
|
+
throw new IllegalArgumentException("mask() can only be used with an image that's the same size.");
|
948
|
+
}
|
949
|
+
for (int i = 0; i < pixels.length; i++) {
|
950
|
+
pixels[i] = ((maskArray[i] & 0xff) << 24) | (pixels[i] & 0xffffff);
|
951
|
+
}
|
952
|
+
format = ARGB;
|
953
|
+
updatePixels();
|
954
|
+
}
|
955
|
+
|
956
|
+
|
957
|
+
/**
|
958
|
+
* ( begin auto-generated from PImage_mask.xml )
|
959
|
+
*
|
960
|
+
* Masks part of an image from displaying by loading another image and
|
961
|
+
* using it as an alpha channel. This mask image should only contain
|
962
|
+
* grayscale data, but only the blue color channel is used. The mask image
|
963
|
+
* needs to be the same size as the image to which it is applied.<br />
|
964
|
+
* <br />
|
965
|
+
* In addition to using a mask image, an integer array containing the alpha
|
966
|
+
* channel data can be specified directly. This method is useful for
|
967
|
+
* creating dynamically generated alpha masks. This array must be of the
|
968
|
+
* same length as the target image's pixels array and should contain only
|
969
|
+
* grayscale data of values between 0-255.
|
970
|
+
*
|
971
|
+
* ( end auto-generated )
|
972
|
+
*
|
973
|
+
* <h3>Advanced</h3>
|
974
|
+
*
|
975
|
+
* Set alpha channel for an image. Black colors in the source
|
976
|
+
* image will make the destination image completely transparent,
|
977
|
+
* and white will make things fully opaque. Gray values will
|
978
|
+
* be in-between steps.
|
979
|
+
* <P>
|
980
|
+
* Strictly speaking the "blue" value from the source image is
|
981
|
+
* used as the alpha color. For a fully grayscale image, this
|
982
|
+
* is correct, but for a color image it's not 100% accurate.
|
983
|
+
* For a more accurate conversion, first use filter(GRAY)
|
984
|
+
* which will make the image into a "correct" grayscale by
|
985
|
+
* performing a proper luminance-based conversion.
|
986
|
+
*
|
987
|
+
* @webref pimage:method
|
988
|
+
* @usage web_application
|
989
|
+
* @param img image to use as the mask
|
990
|
+
* @brief Masks part of an image with another image as an alpha channel
|
991
|
+
*/
|
992
|
+
public void mask(PImage img) {
|
993
|
+
img.loadPixels();
|
994
|
+
mask(img.pixels);
|
995
|
+
}
|
996
|
+
|
997
|
+
|
998
|
+
|
999
|
+
//////////////////////////////////////////////////////////////
|
1000
|
+
|
1001
|
+
// IMAGE FILTERS
|
1002
|
+
|
1003
|
+
|
1004
|
+
public void filter(int kind) {
|
1005
|
+
loadPixels();
|
1006
|
+
|
1007
|
+
switch (kind) {
|
1008
|
+
case BLUR:
|
1009
|
+
// TODO write basic low-pass filter blur here
|
1010
|
+
// what does photoshop do on the edges with this guy?
|
1011
|
+
// better yet.. why bother? just use gaussian with radius 1
|
1012
|
+
filter(BLUR, 1);
|
1013
|
+
break;
|
1014
|
+
|
1015
|
+
case GRAY:
|
1016
|
+
if (format == ALPHA) {
|
1017
|
+
// for an alpha image, convert it to an opaque grayscale
|
1018
|
+
for (int i = 0; i < pixels.length; i++) {
|
1019
|
+
int col = 255 - pixels[i];
|
1020
|
+
pixels[i] = 0xff000000 | (col << 16) | (col << 8) | col;
|
1021
|
+
}
|
1022
|
+
format = RGB;
|
1023
|
+
|
1024
|
+
} else {
|
1025
|
+
// Converts RGB image data into grayscale using
|
1026
|
+
// weighted RGB components, and keeps alpha channel intact.
|
1027
|
+
// [toxi 040115]
|
1028
|
+
for (int i = 0; i < pixels.length; i++) {
|
1029
|
+
int col = pixels[i];
|
1030
|
+
// luminance = 0.3*red + 0.59*green + 0.11*blue
|
1031
|
+
// 0.30 * 256 = 77
|
1032
|
+
// 0.59 * 256 = 151
|
1033
|
+
// 0.11 * 256 = 28
|
1034
|
+
int lum = (77*(col>>16&0xff) + 151*(col>>8&0xff) + 28*(col&0xff))>>8;
|
1035
|
+
pixels[i] = (col & ALPHA_MASK) | lum<<16 | lum<<8 | lum;
|
1036
|
+
}
|
1037
|
+
}
|
1038
|
+
break;
|
1039
|
+
|
1040
|
+
case INVERT:
|
1041
|
+
for (int i = 0; i < pixels.length; i++) {
|
1042
|
+
//pixels[i] = 0xff000000 |
|
1043
|
+
pixels[i] ^= 0xffffff;
|
1044
|
+
}
|
1045
|
+
break;
|
1046
|
+
|
1047
|
+
case POSTERIZE:
|
1048
|
+
throw new RuntimeException("Use filter(POSTERIZE, int levels) " +
|
1049
|
+
"instead of filter(POSTERIZE)");
|
1050
|
+
|
1051
|
+
case OPAQUE:
|
1052
|
+
for (int i = 0; i < pixels.length; i++) {
|
1053
|
+
pixels[i] |= 0xff000000;
|
1054
|
+
}
|
1055
|
+
format = RGB;
|
1056
|
+
break;
|
1057
|
+
|
1058
|
+
case THRESHOLD:
|
1059
|
+
filter(THRESHOLD, 0.5f);
|
1060
|
+
break;
|
1061
|
+
|
1062
|
+
// [toxi 050728] added new filters
|
1063
|
+
case ERODE:
|
1064
|
+
erode(); // former dilate(true);
|
1065
|
+
break;
|
1066
|
+
|
1067
|
+
case DILATE:
|
1068
|
+
dilate(); // former dilate(false);
|
1069
|
+
break;
|
1070
|
+
}
|
1071
|
+
updatePixels(); // mark as modified
|
1072
|
+
}
|
1073
|
+
|
1074
|
+
|
1075
|
+
/**
|
1076
|
+
* ( begin auto-generated from PImage_filter.xml )
|
1077
|
+
*
|
1078
|
+
* Filters an image as defined by one of the following modes:<br /><br
|
1079
|
+
* />THRESHOLD - converts the image to black and white pixels depending if
|
1080
|
+
* they are above or below the threshold defined by the level parameter.
|
1081
|
+
* The level must be between 0.0 (black) and 1.0(white). If no level is
|
1082
|
+
* specified, 0.5 is used.<br />
|
1083
|
+
* <br />
|
1084
|
+
* GRAY - converts any colors in the image to grayscale equivalents<br />
|
1085
|
+
* <br />
|
1086
|
+
* INVERT - sets each pixel to its inverse value<br />
|
1087
|
+
* <br />
|
1088
|
+
* POSTERIZE - limits each channel of the image to the number of colors
|
1089
|
+
* specified as the level parameter<br />
|
1090
|
+
* <br />
|
1091
|
+
* BLUR - executes a Guassian blur with the level parameter specifying the
|
1092
|
+
* extent of the blurring. If no level parameter is used, the blur is
|
1093
|
+
* equivalent to Guassian blur of radius 1<br />
|
1094
|
+
* <br />
|
1095
|
+
* OPAQUE - sets the alpha channel to entirely opaque<br />
|
1096
|
+
* <br />
|
1097
|
+
* ERODE - reduces the light areas with the amount defined by the level
|
1098
|
+
* parameter<br />
|
1099
|
+
* <br />
|
1100
|
+
* DILATE - increases the light areas with the amount defined by the level parameter
|
1101
|
+
*
|
1102
|
+
* ( end auto-generated )
|
1103
|
+
*
|
1104
|
+
* <h3>Advanced</h3>
|
1105
|
+
* Method to apply a variety of basic filters to this image.
|
1106
|
+
* <P>
|
1107
|
+
* <UL>
|
1108
|
+
* <LI>filter(BLUR) provides a basic blur.
|
1109
|
+
* <LI>filter(GRAY) converts the image to grayscale based on luminance.
|
1110
|
+
* <LI>filter(INVERT) will invert the color components in the image.
|
1111
|
+
* <LI>filter(OPAQUE) set all the high bits in the image to opaque
|
1112
|
+
* <LI>filter(THRESHOLD) converts the image to black and white.
|
1113
|
+
* <LI>filter(DILATE) grow white/light areas
|
1114
|
+
* <LI>filter(ERODE) shrink white/light areas
|
1115
|
+
* </UL>
|
1116
|
+
* Luminance conversion code contributed by
|
1117
|
+
* <A HREF="http://www.toxi.co.uk">toxi</A>
|
1118
|
+
* <P/>
|
1119
|
+
* Gaussian blur code contributed by
|
1120
|
+
* <A HREF="http://incubator.quasimondo.com">Mario Klingemann</A>
|
1121
|
+
*
|
1122
|
+
* @webref image:pixels
|
1123
|
+
* @brief Converts the image to grayscale or black and white
|
1124
|
+
* @usage web_application
|
1125
|
+
* @param kind Either THRESHOLD, GRAY, OPAQUE, INVERT, POSTERIZE, BLUR, ERODE, or DILATE
|
1126
|
+
* @param param unique for each, see above
|
1127
|
+
*/
|
1128
|
+
public void filter(int kind, float param) {
|
1129
|
+
loadPixels();
|
1130
|
+
|
1131
|
+
switch (kind) {
|
1132
|
+
case BLUR:
|
1133
|
+
if (format == ALPHA)
|
1134
|
+
blurAlpha(param);
|
1135
|
+
else if (format == ARGB)
|
1136
|
+
blurARGB(param);
|
1137
|
+
else
|
1138
|
+
blurRGB(param);
|
1139
|
+
break;
|
1140
|
+
|
1141
|
+
case GRAY:
|
1142
|
+
throw new RuntimeException("Use filter(GRAY) instead of " +
|
1143
|
+
"filter(GRAY, param)");
|
1144
|
+
|
1145
|
+
case INVERT:
|
1146
|
+
throw new RuntimeException("Use filter(INVERT) instead of " +
|
1147
|
+
"filter(INVERT, param)");
|
1148
|
+
|
1149
|
+
case OPAQUE:
|
1150
|
+
throw new RuntimeException("Use filter(OPAQUE) instead of " +
|
1151
|
+
"filter(OPAQUE, param)");
|
1152
|
+
|
1153
|
+
case POSTERIZE:
|
1154
|
+
int levels = (int)param;
|
1155
|
+
if ((levels < 2) || (levels > 255)) {
|
1156
|
+
throw new RuntimeException("Levels must be between 2 and 255 for " +
|
1157
|
+
"filter(POSTERIZE, levels)");
|
1158
|
+
}
|
1159
|
+
int levels1 = levels - 1;
|
1160
|
+
for (int i = 0; i < pixels.length; i++) {
|
1161
|
+
int rlevel = (pixels[i] >> 16) & 0xff;
|
1162
|
+
int glevel = (pixels[i] >> 8) & 0xff;
|
1163
|
+
int blevel = pixels[i] & 0xff;
|
1164
|
+
rlevel = (((rlevel * levels) >> 8) * 255) / levels1;
|
1165
|
+
glevel = (((glevel * levels) >> 8) * 255) / levels1;
|
1166
|
+
blevel = (((blevel * levels) >> 8) * 255) / levels1;
|
1167
|
+
pixels[i] = ((0xff000000 & pixels[i]) |
|
1168
|
+
(rlevel << 16) |
|
1169
|
+
(glevel << 8) |
|
1170
|
+
blevel);
|
1171
|
+
}
|
1172
|
+
break;
|
1173
|
+
|
1174
|
+
case THRESHOLD: // greater than or equal to the threshold
|
1175
|
+
int thresh = (int) (param * 255);
|
1176
|
+
for (int i = 0; i < pixels.length; i++) {
|
1177
|
+
int max = Math.max((pixels[i] & RED_MASK) >> 16,
|
1178
|
+
Math.max((pixels[i] & GREEN_MASK) >> 8,
|
1179
|
+
(pixels[i] & BLUE_MASK)));
|
1180
|
+
pixels[i] = (pixels[i] & ALPHA_MASK) |
|
1181
|
+
((max < thresh) ? 0x000000 : 0xffffff);
|
1182
|
+
}
|
1183
|
+
break;
|
1184
|
+
|
1185
|
+
// [toxi20050728] added new filters
|
1186
|
+
case ERODE:
|
1187
|
+
throw new RuntimeException("Use filter(ERODE) instead of " +
|
1188
|
+
"filter(ERODE, param)");
|
1189
|
+
case DILATE:
|
1190
|
+
throw new RuntimeException("Use filter(DILATE) instead of " +
|
1191
|
+
"filter(DILATE, param)");
|
1192
|
+
}
|
1193
|
+
updatePixels(); // mark as modified
|
1194
|
+
}
|
1195
|
+
|
1196
|
+
|
1197
|
+
/** Set the high bits of all pixels to opaque. */
|
1198
|
+
protected void opaque() {
|
1199
|
+
for (int i = 0; i < pixels.length; i++) {
|
1200
|
+
pixels[i] = 0xFF000000 | pixels[i];
|
1201
|
+
}
|
1202
|
+
}
|
1203
|
+
|
1204
|
+
|
1205
|
+
/**
|
1206
|
+
* Optimized code for building the blur kernel.
|
1207
|
+
* further optimized blur code (approx. 15% for radius=20)
|
1208
|
+
* bigger speed gains for larger radii (~30%)
|
1209
|
+
* added support for various image types (ALPHA, RGB, ARGB)
|
1210
|
+
* [toxi 050728]
|
1211
|
+
*/
|
1212
|
+
protected void buildBlurKernel(float r) {
|
1213
|
+
int radius = (int) (r * 3.5f);
|
1214
|
+
radius = (radius < 1) ? 1 : ((radius < 248) ? radius : 248);
|
1215
|
+
if (blurRadius != radius) {
|
1216
|
+
blurRadius = radius;
|
1217
|
+
blurKernelSize = 1 + blurRadius<<1;
|
1218
|
+
blurKernel = new int[blurKernelSize];
|
1219
|
+
blurMult = new int[blurKernelSize][256];
|
1220
|
+
|
1221
|
+
int bk,bki;
|
1222
|
+
int[] bm,bmi;
|
1223
|
+
|
1224
|
+
for (int i = 1, radiusi = radius - 1; i < radius; i++) {
|
1225
|
+
blurKernel[radius+i] = blurKernel[radiusi] = bki = radiusi * radiusi;
|
1226
|
+
bm=blurMult[radius+i];
|
1227
|
+
bmi=blurMult[radiusi--];
|
1228
|
+
for (int j = 0; j < 256; j++)
|
1229
|
+
bm[j] = bmi[j] = bki*j;
|
1230
|
+
}
|
1231
|
+
bk = blurKernel[radius] = radius * radius;
|
1232
|
+
bm = blurMult[radius];
|
1233
|
+
for (int j = 0; j < 256; j++)
|
1234
|
+
bm[j] = bk*j;
|
1235
|
+
}
|
1236
|
+
}
|
1237
|
+
|
1238
|
+
|
1239
|
+
protected void blurAlpha(float r) {
|
1240
|
+
int sum, cb;
|
1241
|
+
int read, ri, ym, ymi, bk0;
|
1242
|
+
int b2[] = new int[pixels.length];
|
1243
|
+
int yi = 0;
|
1244
|
+
|
1245
|
+
buildBlurKernel(r);
|
1246
|
+
|
1247
|
+
for (int y = 0; y < pixelHeight; y++) {
|
1248
|
+
for (int x = 0; x < pixelWidth; x++) {
|
1249
|
+
//cb = cg = cr = sum = 0;
|
1250
|
+
cb = sum = 0;
|
1251
|
+
read = x - blurRadius;
|
1252
|
+
if (read<0) {
|
1253
|
+
bk0=-read;
|
1254
|
+
read=0;
|
1255
|
+
} else {
|
1256
|
+
if (read >= pixelWidth)
|
1257
|
+
break;
|
1258
|
+
bk0=0;
|
1259
|
+
}
|
1260
|
+
for (int i = bk0; i < blurKernelSize; i++) {
|
1261
|
+
if (read >= pixelWidth)
|
1262
|
+
break;
|
1263
|
+
int c = pixels[read + yi];
|
1264
|
+
int[] bm = blurMult[i];
|
1265
|
+
cb += bm[c & BLUE_MASK];
|
1266
|
+
sum += blurKernel[i];
|
1267
|
+
read++;
|
1268
|
+
}
|
1269
|
+
ri = yi + x;
|
1270
|
+
b2[ri] = cb / sum;
|
1271
|
+
}
|
1272
|
+
yi += pixelWidth;
|
1273
|
+
}
|
1274
|
+
|
1275
|
+
yi = 0;
|
1276
|
+
ym = -blurRadius;
|
1277
|
+
ymi = ym * pixelWidth;
|
1278
|
+
|
1279
|
+
for (int y = 0; y < pixelHeight; y++) {
|
1280
|
+
for (int x = 0; x < pixelWidth; x++) {
|
1281
|
+
cb = sum = 0;
|
1282
|
+
if (ym < 0) {
|
1283
|
+
bk0 = ri = -ym;
|
1284
|
+
read = x;
|
1285
|
+
} else {
|
1286
|
+
if (ym >= pixelHeight)
|
1287
|
+
break;
|
1288
|
+
bk0 = 0;
|
1289
|
+
ri = ym;
|
1290
|
+
read = x + ymi;
|
1291
|
+
}
|
1292
|
+
for (int i = bk0; i < blurKernelSize; i++) {
|
1293
|
+
if (ri >= pixelHeight)
|
1294
|
+
break;
|
1295
|
+
int[] bm = blurMult[i];
|
1296
|
+
cb += bm[b2[read]];
|
1297
|
+
sum += blurKernel[i];
|
1298
|
+
ri++;
|
1299
|
+
read += pixelWidth;
|
1300
|
+
}
|
1301
|
+
pixels[x+yi] = (cb/sum);
|
1302
|
+
}
|
1303
|
+
yi += pixelWidth;
|
1304
|
+
ymi += pixelWidth;
|
1305
|
+
ym++;
|
1306
|
+
}
|
1307
|
+
}
|
1308
|
+
|
1309
|
+
|
1310
|
+
protected void blurRGB(float r) {
|
1311
|
+
int sum, cr, cg, cb; //, k;
|
1312
|
+
int /*pixel,*/ read, ri, /*roff,*/ ym, ymi, /*riw,*/ bk0;
|
1313
|
+
int r2[] = new int[pixels.length];
|
1314
|
+
int g2[] = new int[pixels.length];
|
1315
|
+
int b2[] = new int[pixels.length];
|
1316
|
+
int yi = 0;
|
1317
|
+
|
1318
|
+
buildBlurKernel(r);
|
1319
|
+
|
1320
|
+
for (int y = 0; y < pixelHeight; y++) {
|
1321
|
+
for (int x = 0; x < pixelWidth; x++) {
|
1322
|
+
cb = cg = cr = sum = 0;
|
1323
|
+
read = x - blurRadius;
|
1324
|
+
if (read < 0) {
|
1325
|
+
bk0 = -read;
|
1326
|
+
read = 0;
|
1327
|
+
} else {
|
1328
|
+
if (read >= pixelWidth) {
|
1329
|
+
break;
|
1330
|
+
}
|
1331
|
+
bk0 = 0;
|
1332
|
+
}
|
1333
|
+
for (int i = bk0; i < blurKernelSize; i++) {
|
1334
|
+
if (read >= pixelWidth) {
|
1335
|
+
break;
|
1336
|
+
}
|
1337
|
+
int c = pixels[read + yi];
|
1338
|
+
int[] bm = blurMult[i];
|
1339
|
+
cr += bm[(c & RED_MASK) >> 16];
|
1340
|
+
cg += bm[(c & GREEN_MASK) >> 8];
|
1341
|
+
cb += bm[c & BLUE_MASK];
|
1342
|
+
sum += blurKernel[i];
|
1343
|
+
read++;
|
1344
|
+
}
|
1345
|
+
ri = yi + x;
|
1346
|
+
r2[ri] = cr / sum;
|
1347
|
+
g2[ri] = cg / sum;
|
1348
|
+
b2[ri] = cb / sum;
|
1349
|
+
}
|
1350
|
+
yi += pixelWidth;
|
1351
|
+
}
|
1352
|
+
|
1353
|
+
yi = 0;
|
1354
|
+
ym = -blurRadius;
|
1355
|
+
ymi = ym * pixelWidth;
|
1356
|
+
|
1357
|
+
for (int y = 0; y < pixelHeight; y++) {
|
1358
|
+
for (int x = 0; x < pixelWidth; x++) {
|
1359
|
+
cb = cg = cr = sum = 0;
|
1360
|
+
if (ym < 0) {
|
1361
|
+
bk0 = ri = -ym;
|
1362
|
+
read = x;
|
1363
|
+
} else {
|
1364
|
+
if (ym >= pixelHeight) {
|
1365
|
+
break;
|
1366
|
+
}
|
1367
|
+
bk0 = 0;
|
1368
|
+
ri = ym;
|
1369
|
+
read = x + ymi;
|
1370
|
+
}
|
1371
|
+
for (int i = bk0; i < blurKernelSize; i++) {
|
1372
|
+
if (ri >= pixelHeight) {
|
1373
|
+
break;
|
1374
|
+
}
|
1375
|
+
int[] bm = blurMult[i];
|
1376
|
+
cr += bm[r2[read]];
|
1377
|
+
cg += bm[g2[read]];
|
1378
|
+
cb += bm[b2[read]];
|
1379
|
+
sum += blurKernel[i];
|
1380
|
+
ri++;
|
1381
|
+
read += pixelWidth;
|
1382
|
+
}
|
1383
|
+
pixels[x+yi] = 0xff000000 | (cr/sum)<<16 | (cg/sum)<<8 | (cb/sum);
|
1384
|
+
}
|
1385
|
+
yi += pixelWidth;
|
1386
|
+
ymi += pixelWidth;
|
1387
|
+
ym++;
|
1388
|
+
}
|
1389
|
+
}
|
1390
|
+
|
1391
|
+
|
1392
|
+
protected void blurARGB(float r) {
|
1393
|
+
int sum, cr, cg, cb, ca;
|
1394
|
+
int /*pixel,*/ read, ri, /*roff,*/ ym, ymi, /*riw,*/ bk0;
|
1395
|
+
int wh = pixels.length;
|
1396
|
+
int r2[] = new int[wh];
|
1397
|
+
int g2[] = new int[wh];
|
1398
|
+
int b2[] = new int[wh];
|
1399
|
+
int a2[] = new int[wh];
|
1400
|
+
int yi = 0;
|
1401
|
+
|
1402
|
+
buildBlurKernel(r);
|
1403
|
+
|
1404
|
+
for (int y = 0; y < pixelHeight; y++) {
|
1405
|
+
for (int x = 0; x < pixelWidth; x++) {
|
1406
|
+
cb = cg = cr = ca = sum = 0;
|
1407
|
+
read = x - blurRadius;
|
1408
|
+
if (read < 0) {
|
1409
|
+
bk0 = -read;
|
1410
|
+
read = 0;
|
1411
|
+
} else {
|
1412
|
+
if (read >= pixelWidth) {
|
1413
|
+
break;
|
1414
|
+
}
|
1415
|
+
bk0=0;
|
1416
|
+
}
|
1417
|
+
for (int i = bk0; i < blurKernelSize; i++) {
|
1418
|
+
if (read >= pixelWidth) {
|
1419
|
+
break;
|
1420
|
+
}
|
1421
|
+
int c = pixels[read + yi];
|
1422
|
+
int[] bm=blurMult[i];
|
1423
|
+
ca += bm[(c & ALPHA_MASK) >>> 24];
|
1424
|
+
cr += bm[(c & RED_MASK) >> 16];
|
1425
|
+
cg += bm[(c & GREEN_MASK) >> 8];
|
1426
|
+
cb += bm[c & BLUE_MASK];
|
1427
|
+
sum += blurKernel[i];
|
1428
|
+
read++;
|
1429
|
+
}
|
1430
|
+
ri = yi + x;
|
1431
|
+
a2[ri] = ca / sum;
|
1432
|
+
r2[ri] = cr / sum;
|
1433
|
+
g2[ri] = cg / sum;
|
1434
|
+
b2[ri] = cb / sum;
|
1435
|
+
}
|
1436
|
+
yi += pixelWidth;
|
1437
|
+
}
|
1438
|
+
|
1439
|
+
yi = 0;
|
1440
|
+
ym = -blurRadius;
|
1441
|
+
ymi = ym * pixelWidth;
|
1442
|
+
|
1443
|
+
for (int y = 0; y < pixelHeight; y++) {
|
1444
|
+
for (int x = 0; x < pixelWidth; x++) {
|
1445
|
+
cb = cg = cr = ca = sum = 0;
|
1446
|
+
if (ym < 0) {
|
1447
|
+
bk0 = ri = -ym;
|
1448
|
+
read = x;
|
1449
|
+
} else {
|
1450
|
+
if (ym >= pixelHeight) {
|
1451
|
+
break;
|
1452
|
+
}
|
1453
|
+
bk0 = 0;
|
1454
|
+
ri = ym;
|
1455
|
+
read = x + ymi;
|
1456
|
+
}
|
1457
|
+
for (int i = bk0; i < blurKernelSize; i++) {
|
1458
|
+
if (ri >= pixelHeight) {
|
1459
|
+
break;
|
1460
|
+
}
|
1461
|
+
int[] bm=blurMult[i];
|
1462
|
+
ca += bm[a2[read]];
|
1463
|
+
cr += bm[r2[read]];
|
1464
|
+
cg += bm[g2[read]];
|
1465
|
+
cb += bm[b2[read]];
|
1466
|
+
sum += blurKernel[i];
|
1467
|
+
ri++;
|
1468
|
+
read += pixelWidth;
|
1469
|
+
}
|
1470
|
+
pixels[x+yi] = (ca/sum)<<24 | (cr/sum)<<16 | (cg/sum)<<8 | (cb/sum);
|
1471
|
+
}
|
1472
|
+
yi += pixelWidth;
|
1473
|
+
ymi += pixelWidth;
|
1474
|
+
ym++;
|
1475
|
+
}
|
1476
|
+
}
|
1477
|
+
|
1478
|
+
|
1479
|
+
/**
|
1480
|
+
* Generic dilate/erode filter using luminance values
|
1481
|
+
* as decision factor. [toxi 050728]
|
1482
|
+
*/
|
1483
|
+
protected void dilate() { // formerly dilate(false)
|
1484
|
+
int index = 0;
|
1485
|
+
int maxIndex = pixels.length;
|
1486
|
+
int[] outgoing = new int[maxIndex];
|
1487
|
+
|
1488
|
+
// erosion (grow light areas)
|
1489
|
+
while (index < maxIndex) {
|
1490
|
+
int curRowIndex = index;
|
1491
|
+
int maxRowIndex = index + pixelWidth;
|
1492
|
+
while (index < maxRowIndex) {
|
1493
|
+
int orig = pixels[index];
|
1494
|
+
int result = orig;
|
1495
|
+
int idxLeft = index - 1;
|
1496
|
+
int idxRight = index + 1;
|
1497
|
+
int idxUp = index - pixelWidth;
|
1498
|
+
int idxDown = index + pixelWidth;
|
1499
|
+
if (idxLeft < curRowIndex) {
|
1500
|
+
idxLeft = index;
|
1501
|
+
}
|
1502
|
+
if (idxRight >= maxRowIndex) {
|
1503
|
+
idxRight = index;
|
1504
|
+
}
|
1505
|
+
if (idxUp < 0) {
|
1506
|
+
idxUp = index;
|
1507
|
+
}
|
1508
|
+
if (idxDown >= maxIndex) {
|
1509
|
+
idxDown = index;
|
1510
|
+
}
|
1511
|
+
|
1512
|
+
int colUp = pixels[idxUp];
|
1513
|
+
int colLeft = pixels[idxLeft];
|
1514
|
+
int colDown = pixels[idxDown];
|
1515
|
+
int colRight = pixels[idxRight];
|
1516
|
+
|
1517
|
+
// compute luminance
|
1518
|
+
int currLum =
|
1519
|
+
77*(orig>>16&0xff) + 151*(orig>>8&0xff) + 28*(orig&0xff);
|
1520
|
+
int lumLeft =
|
1521
|
+
77*(colLeft>>16&0xff) + 151*(colLeft>>8&0xff) + 28*(colLeft&0xff);
|
1522
|
+
int lumRight =
|
1523
|
+
77*(colRight>>16&0xff) + 151*(colRight>>8&0xff) + 28*(colRight&0xff);
|
1524
|
+
int lumUp =
|
1525
|
+
77*(colUp>>16&0xff) + 151*(colUp>>8&0xff) + 28*(colUp&0xff);
|
1526
|
+
int lumDown =
|
1527
|
+
77*(colDown>>16&0xff) + 151*(colDown>>8&0xff) + 28*(colDown&0xff);
|
1528
|
+
|
1529
|
+
if (lumLeft > currLum) {
|
1530
|
+
result = colLeft;
|
1531
|
+
currLum = lumLeft;
|
1532
|
+
}
|
1533
|
+
if (lumRight > currLum) {
|
1534
|
+
result = colRight;
|
1535
|
+
currLum = lumRight;
|
1536
|
+
}
|
1537
|
+
if (lumUp > currLum) {
|
1538
|
+
result = colUp;
|
1539
|
+
currLum = lumUp;
|
1540
|
+
}
|
1541
|
+
if (lumDown > currLum) {
|
1542
|
+
result = colDown;
|
1543
|
+
currLum = lumDown;
|
1544
|
+
}
|
1545
|
+
outgoing[index++] = result;
|
1546
|
+
}
|
1547
|
+
}
|
1548
|
+
System.arraycopy(outgoing, 0, pixels, 0, maxIndex);
|
1549
|
+
}
|
1550
|
+
|
1551
|
+
|
1552
|
+
protected void erode() { // formerly dilate(true)
|
1553
|
+
int index = 0;
|
1554
|
+
int maxIndex = pixels.length;
|
1555
|
+
int[] outgoing = new int[maxIndex];
|
1556
|
+
|
1557
|
+
// dilate (grow dark areas)
|
1558
|
+
while (index < maxIndex) {
|
1559
|
+
int curRowIndex = index;
|
1560
|
+
int maxRowIndex = index + pixelWidth;
|
1561
|
+
while (index < maxRowIndex) {
|
1562
|
+
int orig = pixels[index];
|
1563
|
+
int result = orig;
|
1564
|
+
int idxLeft = index - 1;
|
1565
|
+
int idxRight = index + 1;
|
1566
|
+
int idxUp = index - pixelWidth;
|
1567
|
+
int idxDown = index + pixelWidth;
|
1568
|
+
if (idxLeft < curRowIndex) {
|
1569
|
+
idxLeft = index;
|
1570
|
+
}
|
1571
|
+
if (idxRight >= maxRowIndex) {
|
1572
|
+
idxRight = index;
|
1573
|
+
}
|
1574
|
+
if (idxUp < 0) {
|
1575
|
+
idxUp = index;
|
1576
|
+
}
|
1577
|
+
if (idxDown >= maxIndex) {
|
1578
|
+
idxDown = index;
|
1579
|
+
}
|
1580
|
+
|
1581
|
+
int colUp = pixels[idxUp];
|
1582
|
+
int colLeft = pixels[idxLeft];
|
1583
|
+
int colDown = pixels[idxDown];
|
1584
|
+
int colRight = pixels[idxRight];
|
1585
|
+
|
1586
|
+
// compute luminance
|
1587
|
+
int currLum =
|
1588
|
+
77*(orig>>16&0xff) + 151*(orig>>8&0xff) + 28*(orig&0xff);
|
1589
|
+
int lumLeft =
|
1590
|
+
77*(colLeft>>16&0xff) + 151*(colLeft>>8&0xff) + 28*(colLeft&0xff);
|
1591
|
+
int lumRight =
|
1592
|
+
77*(colRight>>16&0xff) + 151*(colRight>>8&0xff) + 28*(colRight&0xff);
|
1593
|
+
int lumUp =
|
1594
|
+
77*(colUp>>16&0xff) + 151*(colUp>>8&0xff) + 28*(colUp&0xff);
|
1595
|
+
int lumDown =
|
1596
|
+
77*(colDown>>16&0xff) + 151*(colDown>>8&0xff) + 28*(colDown&0xff);
|
1597
|
+
|
1598
|
+
if (lumLeft < currLum) {
|
1599
|
+
result = colLeft;
|
1600
|
+
currLum = lumLeft;
|
1601
|
+
}
|
1602
|
+
if (lumRight < currLum) {
|
1603
|
+
result = colRight;
|
1604
|
+
currLum = lumRight;
|
1605
|
+
}
|
1606
|
+
if (lumUp < currLum) {
|
1607
|
+
result = colUp;
|
1608
|
+
currLum = lumUp;
|
1609
|
+
}
|
1610
|
+
if (lumDown < currLum) {
|
1611
|
+
result = colDown;
|
1612
|
+
currLum = lumDown;
|
1613
|
+
}
|
1614
|
+
outgoing[index++] = result;
|
1615
|
+
}
|
1616
|
+
}
|
1617
|
+
System.arraycopy(outgoing, 0, pixels, 0, maxIndex);
|
1618
|
+
}
|
1619
|
+
|
1620
|
+
|
1621
|
+
|
1622
|
+
//////////////////////////////////////////////////////////////
|
1623
|
+
|
1624
|
+
// COPY
|
1625
|
+
|
1626
|
+
|
1627
|
+
/**
|
1628
|
+
* ( begin auto-generated from PImage_copy.xml )
|
1629
|
+
*
|
1630
|
+
* Copies a region of pixels from one image into another. If the source and
|
1631
|
+
* destination regions aren't the same size, it will automatically resize
|
1632
|
+
* source pixels to fit the specified target region. No alpha information
|
1633
|
+
* is used in the process, however if the source image has an alpha channel
|
1634
|
+
* set, it will be copied as well.
|
1635
|
+
* <br /><br />
|
1636
|
+
* As of release 0149, this function ignores <b>imageMode()</b>.
|
1637
|
+
*
|
1638
|
+
* ( end auto-generated )
|
1639
|
+
*
|
1640
|
+
* @webref image:pixels
|
1641
|
+
* @brief Copies the entire image
|
1642
|
+
* @usage web_application
|
1643
|
+
* @param sx X coordinate of the source's upper left corner
|
1644
|
+
* @param sy Y coordinate of the source's upper left corner
|
1645
|
+
* @param sw source image width
|
1646
|
+
* @param sh source image height
|
1647
|
+
* @param dx X coordinate of the destination's upper left corner
|
1648
|
+
* @param dy Y coordinate of the destination's upper left corner
|
1649
|
+
* @param dw destination image width
|
1650
|
+
* @param dh destination image height
|
1651
|
+
* @see PGraphics#alpha(int)
|
1652
|
+
* @see PImage#blend(PImage, int, int, int, int, int, int, int, int, int)
|
1653
|
+
*/
|
1654
|
+
public void copy(int sx, int sy, int sw, int sh,
|
1655
|
+
int dx, int dy, int dw, int dh) {
|
1656
|
+
blend(this, sx, sy, sw, sh, dx, dy, dw, dh, REPLACE);
|
1657
|
+
}
|
1658
|
+
|
1659
|
+
|
1660
|
+
/**
|
1661
|
+
* @param src an image variable referring to the source image.
|
1662
|
+
*/
|
1663
|
+
public void copy(PImage src,
|
1664
|
+
int sx, int sy, int sw, int sh,
|
1665
|
+
int dx, int dy, int dw, int dh) {
|
1666
|
+
blend(src, sx, sy, sw, sh, dx, dy, dw, dh, REPLACE);
|
1667
|
+
}
|
1668
|
+
|
1669
|
+
|
1670
|
+
|
1671
|
+
//////////////////////////////////////////////////////////////
|
1672
|
+
|
1673
|
+
// BLEND
|
1674
|
+
|
1675
|
+
|
1676
|
+
/**
|
1677
|
+
* ( begin auto-generated from blendColor.xml )
|
1678
|
+
*
|
1679
|
+
* Blends two color values together based on the blending mode given as the
|
1680
|
+
* <b>MODE</b> parameter. The possible modes are described in the reference
|
1681
|
+
* for the <b>blend()</b> function.
|
1682
|
+
*
|
1683
|
+
* ( end auto-generated )
|
1684
|
+
* <h3>Advanced</h3>
|
1685
|
+
* <UL>
|
1686
|
+
* <LI>REPLACE - destination colour equals colour of source pixel: C = A.
|
1687
|
+
* Sometimes called "Normal" or "Copy" in other software.
|
1688
|
+
*
|
1689
|
+
* <LI>BLEND - linear interpolation of colours:
|
1690
|
+
* <TT>C = A*factor + B</TT>
|
1691
|
+
*
|
1692
|
+
* <LI>ADD - additive blending with white clip:
|
1693
|
+
* <TT>C = min(A*factor + B, 255)</TT>.
|
1694
|
+
* Clipped to 0..255, Photoshop calls this "Linear Burn",
|
1695
|
+
* and Director calls it "Add Pin".
|
1696
|
+
*
|
1697
|
+
* <LI>SUBTRACT - substractive blend with black clip:
|
1698
|
+
* <TT>C = max(B - A*factor, 0)</TT>.
|
1699
|
+
* Clipped to 0..255, Photoshop calls this "Linear Dodge",
|
1700
|
+
* and Director calls it "Subtract Pin".
|
1701
|
+
*
|
1702
|
+
* <LI>DARKEST - only the darkest colour succeeds:
|
1703
|
+
* <TT>C = min(A*factor, B)</TT>.
|
1704
|
+
* Illustrator calls this "Darken".
|
1705
|
+
*
|
1706
|
+
* <LI>LIGHTEST - only the lightest colour succeeds:
|
1707
|
+
* <TT>C = max(A*factor, B)</TT>.
|
1708
|
+
* Illustrator calls this "Lighten".
|
1709
|
+
*
|
1710
|
+
* <LI>DIFFERENCE - subtract colors from underlying image.
|
1711
|
+
*
|
1712
|
+
* <LI>EXCLUSION - similar to DIFFERENCE, but less extreme.
|
1713
|
+
*
|
1714
|
+
* <LI>MULTIPLY - Multiply the colors, result will always be darker.
|
1715
|
+
*
|
1716
|
+
* <LI>SCREEN - Opposite multiply, uses inverse values of the colors.
|
1717
|
+
*
|
1718
|
+
* <LI>OVERLAY - A mix of MULTIPLY and SCREEN. Multiplies dark values,
|
1719
|
+
* and screens light values.
|
1720
|
+
*
|
1721
|
+
* <LI>HARD_LIGHT - SCREEN when greater than 50% gray, MULTIPLY when lower.
|
1722
|
+
*
|
1723
|
+
* <LI>SOFT_LIGHT - Mix of DARKEST and LIGHTEST.
|
1724
|
+
* Works like OVERLAY, but not as harsh.
|
1725
|
+
*
|
1726
|
+
* <LI>DODGE - Lightens light tones and increases contrast, ignores darks.
|
1727
|
+
* Called "Color Dodge" in Illustrator and Photoshop.
|
1728
|
+
*
|
1729
|
+
* <LI>BURN - Darker areas are applied, increasing contrast, ignores lights.
|
1730
|
+
* Called "Color Burn" in Illustrator and Photoshop.
|
1731
|
+
* </UL>
|
1732
|
+
* <P>A useful reference for blending modes and their algorithms can be
|
1733
|
+
* found in the <A HREF="http://www.w3.org/TR/SVG12/rendering.html">SVG</A>
|
1734
|
+
* specification.</P>
|
1735
|
+
* <P>It is important to note that Processing uses "fast" code, not
|
1736
|
+
* necessarily "correct" code. No biggie, most software does. A nitpicker
|
1737
|
+
* can find numerous "off by 1 division" problems in the blend code where
|
1738
|
+
* <TT>>>8</TT> or <TT>>>7</TT> is used when strictly speaking
|
1739
|
+
* <TT>/255.0</T> or <TT>/127.0</TT> should have been used.</P>
|
1740
|
+
* <P>For instance, exclusion (not intended for real-time use) reads
|
1741
|
+
* <TT>r1 + r2 - ((2 * r1 * r2) / 255)</TT> because <TT>255 == 1.0</TT>
|
1742
|
+
* not <TT>256 == 1.0</TT>. In other words, <TT>(255*255)>>8</TT> is not
|
1743
|
+
* the same as <TT>(255*255)/255</TT>. But for real-time use the shifts
|
1744
|
+
* are preferrable, and the difference is insignificant for applications
|
1745
|
+
* built with Processing.</P>
|
1746
|
+
*
|
1747
|
+
* @webref color:creating_reading
|
1748
|
+
* @usage web_application
|
1749
|
+
* @param c1 the first color to blend
|
1750
|
+
* @param c2 the second color to blend
|
1751
|
+
* @param mode either BLEND, ADD, SUBTRACT, DARKEST, LIGHTEST, DIFFERENCE, EXCLUSION, MULTIPLY, SCREEN, OVERLAY, HARD_LIGHT, SOFT_LIGHT, DODGE, or BURN
|
1752
|
+
* @see PImage#blend(PImage, int, int, int, int, int, int, int, int, int)
|
1753
|
+
* @see PApplet#color(float, float, float, float)
|
1754
|
+
*/
|
1755
|
+
static public int blendColor(int c1, int c2, int mode) { // ignore
|
1756
|
+
switch (mode) {
|
1757
|
+
case REPLACE: return c2;
|
1758
|
+
case BLEND: return blend_blend(c1, c2);
|
1759
|
+
|
1760
|
+
case ADD: return blend_add_pin(c1, c2);
|
1761
|
+
case SUBTRACT: return blend_sub_pin(c1, c2);
|
1762
|
+
|
1763
|
+
case LIGHTEST: return blend_lightest(c1, c2);
|
1764
|
+
case DARKEST: return blend_darkest(c1, c2);
|
1765
|
+
|
1766
|
+
case DIFFERENCE: return blend_difference(c1, c2);
|
1767
|
+
case EXCLUSION: return blend_exclusion(c1, c2);
|
1768
|
+
|
1769
|
+
case MULTIPLY: return blend_multiply(c1, c2);
|
1770
|
+
case SCREEN: return blend_screen(c1, c2);
|
1771
|
+
|
1772
|
+
case HARD_LIGHT: return blend_hard_light(c1, c2);
|
1773
|
+
case SOFT_LIGHT: return blend_soft_light(c1, c2);
|
1774
|
+
case OVERLAY: return blend_overlay(c1, c2);
|
1775
|
+
|
1776
|
+
case DODGE: return blend_dodge(c1, c2);
|
1777
|
+
case BURN: return blend_burn(c1, c2);
|
1778
|
+
}
|
1779
|
+
return 0;
|
1780
|
+
}
|
1781
|
+
|
1782
|
+
|
1783
|
+
public void blend(int sx, int sy, int sw, int sh,
|
1784
|
+
int dx, int dy, int dw, int dh, int mode) {
|
1785
|
+
blend(this, sx, sy, sw, sh, dx, dy, dw, dh, mode);
|
1786
|
+
}
|
1787
|
+
|
1788
|
+
|
1789
|
+
/**
|
1790
|
+
* ( begin auto-generated from PImage_blend.xml )
|
1791
|
+
*
|
1792
|
+
* Blends a region of pixels into the image specified by the <b>img</b>
|
1793
|
+
* parameter. These copies utilize full alpha channel support and a choice
|
1794
|
+
* of the following modes to blend the colors of source pixels (A) with the
|
1795
|
+
* ones of pixels in the destination image (B):<br />
|
1796
|
+
* <br />
|
1797
|
+
* BLEND - linear interpolation of colours: C = A*factor + B<br />
|
1798
|
+
* <br />
|
1799
|
+
* ADD - additive blending with white clip: C = min(A*factor + B, 255)<br />
|
1800
|
+
* <br />
|
1801
|
+
* SUBTRACT - subtractive blending with black clip: C = max(B - A*factor,
|
1802
|
+
* 0)<br />
|
1803
|
+
* <br />
|
1804
|
+
* DARKEST - only the darkest colour succeeds: C = min(A*factor, B)<br />
|
1805
|
+
* <br />
|
1806
|
+
* LIGHTEST - only the lightest colour succeeds: C = max(A*factor, B)<br />
|
1807
|
+
* <br />
|
1808
|
+
* DIFFERENCE - subtract colors from underlying image.<br />
|
1809
|
+
* <br />
|
1810
|
+
* EXCLUSION - similar to DIFFERENCE, but less extreme.<br />
|
1811
|
+
* <br />
|
1812
|
+
* MULTIPLY - Multiply the colors, result will always be darker.<br />
|
1813
|
+
* <br />
|
1814
|
+
* SCREEN - Opposite multiply, uses inverse values of the colors.<br />
|
1815
|
+
* <br />
|
1816
|
+
* OVERLAY - A mix of MULTIPLY and SCREEN. Multiplies dark values,
|
1817
|
+
* and screens light values.<br />
|
1818
|
+
* <br />
|
1819
|
+
* HARD_LIGHT - SCREEN when greater than 50% gray, MULTIPLY when lower.<br />
|
1820
|
+
* <br />
|
1821
|
+
* SOFT_LIGHT - Mix of DARKEST and LIGHTEST.
|
1822
|
+
* Works like OVERLAY, but not as harsh.<br />
|
1823
|
+
* <br />
|
1824
|
+
* DODGE - Lightens light tones and increases contrast, ignores darks.
|
1825
|
+
* Called "Color Dodge" in Illustrator and Photoshop.<br />
|
1826
|
+
* <br />
|
1827
|
+
* BURN - Darker areas are applied, increasing contrast, ignores lights.
|
1828
|
+
* Called "Color Burn" in Illustrator and Photoshop.<br />
|
1829
|
+
* <br />
|
1830
|
+
* All modes use the alpha information (highest byte) of source image
|
1831
|
+
* pixels as the blending factor. If the source and destination regions are
|
1832
|
+
* different sizes, the image will be automatically resized to match the
|
1833
|
+
* destination size. If the <b>srcImg</b> parameter is not used, the
|
1834
|
+
* display window is used as the source image.<br />
|
1835
|
+
* <br />
|
1836
|
+
* As of release 0149, this function ignores <b>imageMode()</b>.
|
1837
|
+
*
|
1838
|
+
* ( end auto-generated )
|
1839
|
+
*
|
1840
|
+
* @webref image:pixels
|
1841
|
+
* @brief Copies a pixel or rectangle of pixels using different blending modes
|
1842
|
+
* @param src an image variable referring to the source image
|
1843
|
+
* @param sx X coordinate of the source's upper left corner
|
1844
|
+
* @param sy Y coordinate of the source's upper left corner
|
1845
|
+
* @param sw source image width
|
1846
|
+
* @param sh source image height
|
1847
|
+
* @param dx X coordinate of the destinations's upper left corner
|
1848
|
+
* @param dy Y coordinate of the destinations's upper left corner
|
1849
|
+
* @param dw destination image width
|
1850
|
+
* @param dh destination image height
|
1851
|
+
* @param mode Either BLEND, ADD, SUBTRACT, LIGHTEST, DARKEST, DIFFERENCE, EXCLUSION, MULTIPLY, SCREEN, OVERLAY, HARD_LIGHT, SOFT_LIGHT, DODGE, BURN
|
1852
|
+
*
|
1853
|
+
* @see PApplet#alpha(int)
|
1854
|
+
* @see PImage#copy(PImage, int, int, int, int, int, int, int, int)
|
1855
|
+
* @see PImage#blendColor(int,int,int)
|
1856
|
+
*/
|
1857
|
+
public void blend(PImage src,
|
1858
|
+
int sx, int sy, int sw, int sh,
|
1859
|
+
int dx, int dy, int dw, int dh, int mode) {
|
1860
|
+
int sx2 = sx + sw;
|
1861
|
+
int sy2 = sy + sh;
|
1862
|
+
int dx2 = dx + dw;
|
1863
|
+
int dy2 = dy + dh;
|
1864
|
+
|
1865
|
+
loadPixels();
|
1866
|
+
if (src == this) {
|
1867
|
+
if (intersect(sx, sy, sx2, sy2, dx, dy, dx2, dy2)) {
|
1868
|
+
blit_resize(get(sx, sy, sw, sh),
|
1869
|
+
0, 0, sw, sh,
|
1870
|
+
pixels, pixelWidth, pixelHeight, dx, dy, dx2, dy2, mode);
|
1871
|
+
} else {
|
1872
|
+
// same as below, except skip the loadPixels() because it'd be redundant
|
1873
|
+
blit_resize(src, sx, sy, sx2, sy2,
|
1874
|
+
pixels, pixelWidth, pixelHeight, dx, dy, dx2, dy2, mode);
|
1875
|
+
}
|
1876
|
+
} else {
|
1877
|
+
src.loadPixels();
|
1878
|
+
blit_resize(src, sx, sy, sx2, sy2,
|
1879
|
+
pixels, pixelWidth, pixelHeight, dx, dy, dx2, dy2, mode);
|
1880
|
+
//src.updatePixels();
|
1881
|
+
}
|
1882
|
+
updatePixels();
|
1883
|
+
}
|
1884
|
+
|
1885
|
+
|
1886
|
+
/**
|
1887
|
+
* Check to see if two rectangles intersect one another
|
1888
|
+
*/
|
1889
|
+
private boolean intersect(int sx1, int sy1, int sx2, int sy2,
|
1890
|
+
int dx1, int dy1, int dx2, int dy2) {
|
1891
|
+
int sw = sx2 - sx1 + 1;
|
1892
|
+
int sh = sy2 - sy1 + 1;
|
1893
|
+
int dw = dx2 - dx1 + 1;
|
1894
|
+
int dh = dy2 - dy1 + 1;
|
1895
|
+
|
1896
|
+
if (dx1 < sx1) {
|
1897
|
+
dw += dx1 - sx1;
|
1898
|
+
if (dw > sw) {
|
1899
|
+
dw = sw;
|
1900
|
+
}
|
1901
|
+
} else {
|
1902
|
+
int w = sw + sx1 - dx1;
|
1903
|
+
if (dw > w) {
|
1904
|
+
dw = w;
|
1905
|
+
}
|
1906
|
+
}
|
1907
|
+
if (dy1 < sy1) {
|
1908
|
+
dh += dy1 - sy1;
|
1909
|
+
if (dh > sh) {
|
1910
|
+
dh = sh;
|
1911
|
+
}
|
1912
|
+
} else {
|
1913
|
+
int h = sh + sy1 - dy1;
|
1914
|
+
if (dh > h) {
|
1915
|
+
dh = h;
|
1916
|
+
}
|
1917
|
+
}
|
1918
|
+
return !(dw <= 0 || dh <= 0);
|
1919
|
+
}
|
1920
|
+
|
1921
|
+
|
1922
|
+
//////////////////////////////////////////////////////////////
|
1923
|
+
|
1924
|
+
|
1925
|
+
/**
|
1926
|
+
* Internal blitter/resizer/copier from toxi.
|
1927
|
+
* Uses bilinear filtering if smooth() has been enabled
|
1928
|
+
* 'mode' determines the blending mode used in the process.
|
1929
|
+
*/
|
1930
|
+
private void blit_resize(PImage img,
|
1931
|
+
int srcX1, int srcY1, int srcX2, int srcY2,
|
1932
|
+
int[] destPixels, int screenW, int screenH,
|
1933
|
+
int destX1, int destY1, int destX2, int destY2,
|
1934
|
+
int mode) {
|
1935
|
+
if (srcX1 < 0) srcX1 = 0;
|
1936
|
+
if (srcY1 < 0) srcY1 = 0;
|
1937
|
+
if (srcX2 > img.pixelWidth) srcX2 = img.pixelWidth;
|
1938
|
+
if (srcY2 > img.pixelHeight) srcY2 = img.pixelHeight;
|
1939
|
+
|
1940
|
+
int srcW = srcX2 - srcX1;
|
1941
|
+
int srcH = srcY2 - srcY1;
|
1942
|
+
int destW = destX2 - destX1;
|
1943
|
+
int destH = destY2 - destY1;
|
1944
|
+
|
1945
|
+
boolean smooth = true; // may as well go with the smoothing these days
|
1946
|
+
|
1947
|
+
if (!smooth) {
|
1948
|
+
srcW++; srcH++;
|
1949
|
+
}
|
1950
|
+
|
1951
|
+
if (destW <= 0 || destH <= 0 ||
|
1952
|
+
srcW <= 0 || srcH <= 0 ||
|
1953
|
+
destX1 >= screenW || destY1 >= screenH ||
|
1954
|
+
srcX1 >= img.pixelWidth || srcY1 >= img.pixelHeight) {
|
1955
|
+
return;
|
1956
|
+
}
|
1957
|
+
|
1958
|
+
int dx = (int) (srcW / (float) destW * PRECISIONF);
|
1959
|
+
int dy = (int) (srcH / (float) destH * PRECISIONF);
|
1960
|
+
|
1961
|
+
srcXOffset = destX1 < 0 ? -destX1 * dx : srcX1 * PRECISIONF;
|
1962
|
+
srcYOffset = destY1 < 0 ? -destY1 * dy : srcY1 * PRECISIONF;
|
1963
|
+
|
1964
|
+
if (destX1 < 0) {
|
1965
|
+
destW += destX1;
|
1966
|
+
destX1 = 0;
|
1967
|
+
}
|
1968
|
+
if (destY1 < 0) {
|
1969
|
+
destH += destY1;
|
1970
|
+
destY1 = 0;
|
1971
|
+
}
|
1972
|
+
|
1973
|
+
destW = min(destW, screenW - destX1);
|
1974
|
+
destH = min(destH, screenH - destY1);
|
1975
|
+
|
1976
|
+
int destOffset = destY1 * screenW + destX1;
|
1977
|
+
srcBuffer = img.pixels;
|
1978
|
+
|
1979
|
+
if (smooth) {
|
1980
|
+
// use bilinear filtering
|
1981
|
+
iw = img.pixelWidth;
|
1982
|
+
iw1 = img.pixelWidth - 1;
|
1983
|
+
ih1 = img.pixelHeight - 1;
|
1984
|
+
|
1985
|
+
switch (mode) {
|
1986
|
+
|
1987
|
+
case BLEND:
|
1988
|
+
for (int y = 0; y < destH; y++) {
|
1989
|
+
filter_new_scanline();
|
1990
|
+
for (int x = 0; x < destW; x++) {
|
1991
|
+
// davbol - renamed old blend_multiply to blend_blend
|
1992
|
+
destPixels[destOffset + x] =
|
1993
|
+
blend_blend(destPixels[destOffset + x], filter_bilinear());
|
1994
|
+
sX += dx;
|
1995
|
+
}
|
1996
|
+
destOffset += screenW;
|
1997
|
+
srcYOffset += dy;
|
1998
|
+
}
|
1999
|
+
break;
|
2000
|
+
|
2001
|
+
case ADD:
|
2002
|
+
for (int y = 0; y < destH; y++) {
|
2003
|
+
filter_new_scanline();
|
2004
|
+
for (int x = 0; x < destW; x++) {
|
2005
|
+
destPixels[destOffset + x] =
|
2006
|
+
blend_add_pin(destPixels[destOffset + x], filter_bilinear());
|
2007
|
+
sX += dx;
|
2008
|
+
}
|
2009
|
+
destOffset += screenW;
|
2010
|
+
srcYOffset += dy;
|
2011
|
+
}
|
2012
|
+
break;
|
2013
|
+
|
2014
|
+
case SUBTRACT:
|
2015
|
+
for (int y = 0; y < destH; y++) {
|
2016
|
+
filter_new_scanline();
|
2017
|
+
for (int x = 0; x < destW; x++) {
|
2018
|
+
destPixels[destOffset + x] =
|
2019
|
+
blend_sub_pin(destPixels[destOffset + x], filter_bilinear());
|
2020
|
+
sX += dx;
|
2021
|
+
}
|
2022
|
+
destOffset += screenW;
|
2023
|
+
srcYOffset += dy;
|
2024
|
+
}
|
2025
|
+
break;
|
2026
|
+
|
2027
|
+
case LIGHTEST:
|
2028
|
+
for (int y = 0; y < destH; y++) {
|
2029
|
+
filter_new_scanline();
|
2030
|
+
for (int x = 0; x < destW; x++) {
|
2031
|
+
destPixels[destOffset + x] =
|
2032
|
+
blend_lightest(destPixels[destOffset + x], filter_bilinear());
|
2033
|
+
sX += dx;
|
2034
|
+
}
|
2035
|
+
destOffset += screenW;
|
2036
|
+
srcYOffset += dy;
|
2037
|
+
}
|
2038
|
+
break;
|
2039
|
+
|
2040
|
+
case DARKEST:
|
2041
|
+
for (int y = 0; y < destH; y++) {
|
2042
|
+
filter_new_scanline();
|
2043
|
+
for (int x = 0; x < destW; x++) {
|
2044
|
+
destPixels[destOffset + x] =
|
2045
|
+
blend_darkest(destPixels[destOffset + x], filter_bilinear());
|
2046
|
+
sX += dx;
|
2047
|
+
}
|
2048
|
+
destOffset += screenW;
|
2049
|
+
srcYOffset += dy;
|
2050
|
+
}
|
2051
|
+
break;
|
2052
|
+
|
2053
|
+
case REPLACE:
|
2054
|
+
for (int y = 0; y < destH; y++) {
|
2055
|
+
filter_new_scanline();
|
2056
|
+
for (int x = 0; x < destW; x++) {
|
2057
|
+
destPixels[destOffset + x] = filter_bilinear();
|
2058
|
+
sX += dx;
|
2059
|
+
}
|
2060
|
+
destOffset += screenW;
|
2061
|
+
srcYOffset += dy;
|
2062
|
+
}
|
2063
|
+
break;
|
2064
|
+
|
2065
|
+
case DIFFERENCE:
|
2066
|
+
for (int y = 0; y < destH; y++) {
|
2067
|
+
filter_new_scanline();
|
2068
|
+
for (int x = 0; x < destW; x++) {
|
2069
|
+
destPixels[destOffset + x] =
|
2070
|
+
blend_difference(destPixels[destOffset + x], filter_bilinear());
|
2071
|
+
sX += dx;
|
2072
|
+
}
|
2073
|
+
destOffset += screenW;
|
2074
|
+
srcYOffset += dy;
|
2075
|
+
}
|
2076
|
+
break;
|
2077
|
+
|
2078
|
+
case EXCLUSION:
|
2079
|
+
for (int y = 0; y < destH; y++) {
|
2080
|
+
filter_new_scanline();
|
2081
|
+
for (int x = 0; x < destW; x++) {
|
2082
|
+
destPixels[destOffset + x] =
|
2083
|
+
blend_exclusion(destPixels[destOffset + x], filter_bilinear());
|
2084
|
+
sX += dx;
|
2085
|
+
}
|
2086
|
+
destOffset += screenW;
|
2087
|
+
srcYOffset += dy;
|
2088
|
+
}
|
2089
|
+
break;
|
2090
|
+
|
2091
|
+
case MULTIPLY:
|
2092
|
+
for (int y = 0; y < destH; y++) {
|
2093
|
+
filter_new_scanline();
|
2094
|
+
for (int x = 0; x < destW; x++) {
|
2095
|
+
destPixels[destOffset + x] =
|
2096
|
+
blend_multiply(destPixels[destOffset + x], filter_bilinear());
|
2097
|
+
sX += dx;
|
2098
|
+
}
|
2099
|
+
destOffset += screenW;
|
2100
|
+
srcYOffset += dy;
|
2101
|
+
}
|
2102
|
+
break;
|
2103
|
+
|
2104
|
+
case SCREEN:
|
2105
|
+
for (int y = 0; y < destH; y++) {
|
2106
|
+
filter_new_scanline();
|
2107
|
+
for (int x = 0; x < destW; x++) {
|
2108
|
+
destPixels[destOffset + x] =
|
2109
|
+
blend_screen(destPixels[destOffset + x], filter_bilinear());
|
2110
|
+
sX += dx;
|
2111
|
+
}
|
2112
|
+
destOffset += screenW;
|
2113
|
+
srcYOffset += dy;
|
2114
|
+
}
|
2115
|
+
break;
|
2116
|
+
|
2117
|
+
case OVERLAY:
|
2118
|
+
for (int y = 0; y < destH; y++) {
|
2119
|
+
filter_new_scanline();
|
2120
|
+
for (int x = 0; x < destW; x++) {
|
2121
|
+
destPixels[destOffset + x] =
|
2122
|
+
blend_overlay(destPixels[destOffset + x], filter_bilinear());
|
2123
|
+
sX += dx;
|
2124
|
+
}
|
2125
|
+
destOffset += screenW;
|
2126
|
+
srcYOffset += dy;
|
2127
|
+
}
|
2128
|
+
break;
|
2129
|
+
|
2130
|
+
case HARD_LIGHT:
|
2131
|
+
for (int y = 0; y < destH; y++) {
|
2132
|
+
filter_new_scanline();
|
2133
|
+
for (int x = 0; x < destW; x++) {
|
2134
|
+
destPixels[destOffset + x] =
|
2135
|
+
blend_hard_light(destPixels[destOffset + x], filter_bilinear());
|
2136
|
+
sX += dx;
|
2137
|
+
}
|
2138
|
+
destOffset += screenW;
|
2139
|
+
srcYOffset += dy;
|
2140
|
+
}
|
2141
|
+
break;
|
2142
|
+
|
2143
|
+
case SOFT_LIGHT:
|
2144
|
+
for (int y = 0; y < destH; y++) {
|
2145
|
+
filter_new_scanline();
|
2146
|
+
for (int x = 0; x < destW; x++) {
|
2147
|
+
destPixels[destOffset + x] =
|
2148
|
+
blend_soft_light(destPixels[destOffset + x], filter_bilinear());
|
2149
|
+
sX += dx;
|
2150
|
+
}
|
2151
|
+
destOffset += screenW;
|
2152
|
+
srcYOffset += dy;
|
2153
|
+
}
|
2154
|
+
break;
|
2155
|
+
|
2156
|
+
// davbol - proposed 2007-01-09
|
2157
|
+
case DODGE:
|
2158
|
+
for (int y = 0; y < destH; y++) {
|
2159
|
+
filter_new_scanline();
|
2160
|
+
for (int x = 0; x < destW; x++) {
|
2161
|
+
destPixels[destOffset + x] =
|
2162
|
+
blend_dodge(destPixels[destOffset + x], filter_bilinear());
|
2163
|
+
sX += dx;
|
2164
|
+
}
|
2165
|
+
destOffset += screenW;
|
2166
|
+
srcYOffset += dy;
|
2167
|
+
}
|
2168
|
+
break;
|
2169
|
+
|
2170
|
+
case BURN:
|
2171
|
+
for (int y = 0; y < destH; y++) {
|
2172
|
+
filter_new_scanline();
|
2173
|
+
for (int x = 0; x < destW; x++) {
|
2174
|
+
destPixels[destOffset + x] =
|
2175
|
+
blend_burn(destPixels[destOffset + x], filter_bilinear());
|
2176
|
+
sX += dx;
|
2177
|
+
}
|
2178
|
+
destOffset += screenW;
|
2179
|
+
srcYOffset += dy;
|
2180
|
+
}
|
2181
|
+
break;
|
2182
|
+
|
2183
|
+
}
|
2184
|
+
|
2185
|
+
} else {
|
2186
|
+
// nearest neighbour scaling (++fast!)
|
2187
|
+
switch (mode) {
|
2188
|
+
|
2189
|
+
case BLEND:
|
2190
|
+
for (int y = 0; y < destH; y++) {
|
2191
|
+
sX = srcXOffset;
|
2192
|
+
sY = (srcYOffset >> PRECISIONB) * img.pixelWidth;
|
2193
|
+
for (int x = 0; x < destW; x++) {
|
2194
|
+
// davbol - renamed old blend_multiply to blend_blend
|
2195
|
+
destPixels[destOffset + x] =
|
2196
|
+
blend_blend(destPixels[destOffset + x],
|
2197
|
+
srcBuffer[sY + (sX >> PRECISIONB)]);
|
2198
|
+
sX += dx;
|
2199
|
+
}
|
2200
|
+
destOffset += screenW;
|
2201
|
+
srcYOffset += dy;
|
2202
|
+
}
|
2203
|
+
break;
|
2204
|
+
|
2205
|
+
case ADD:
|
2206
|
+
for (int y = 0; y < destH; y++) {
|
2207
|
+
sX = srcXOffset;
|
2208
|
+
sY = (srcYOffset >> PRECISIONB) * img.pixelWidth;
|
2209
|
+
for (int x = 0; x < destW; x++) {
|
2210
|
+
destPixels[destOffset + x] =
|
2211
|
+
blend_add_pin(destPixels[destOffset + x],
|
2212
|
+
srcBuffer[sY + (sX >> PRECISIONB)]);
|
2213
|
+
sX += dx;
|
2214
|
+
}
|
2215
|
+
destOffset += screenW;
|
2216
|
+
srcYOffset += dy;
|
2217
|
+
}
|
2218
|
+
break;
|
2219
|
+
|
2220
|
+
case SUBTRACT:
|
2221
|
+
for (int y = 0; y < destH; y++) {
|
2222
|
+
sX = srcXOffset;
|
2223
|
+
sY = (srcYOffset >> PRECISIONB) * img.pixelWidth;
|
2224
|
+
for (int x = 0; x < destW; x++) {
|
2225
|
+
destPixels[destOffset + x] =
|
2226
|
+
blend_sub_pin(destPixels[destOffset + x],
|
2227
|
+
srcBuffer[sY + (sX >> PRECISIONB)]);
|
2228
|
+
sX += dx;
|
2229
|
+
}
|
2230
|
+
destOffset += screenW;
|
2231
|
+
srcYOffset += dy;
|
2232
|
+
}
|
2233
|
+
break;
|
2234
|
+
|
2235
|
+
case LIGHTEST:
|
2236
|
+
for (int y = 0; y < destH; y++) {
|
2237
|
+
sX = srcXOffset;
|
2238
|
+
sY = (srcYOffset >> PRECISIONB) * img.pixelWidth;
|
2239
|
+
for (int x = 0; x < destW; x++) {
|
2240
|
+
destPixels[destOffset + x] =
|
2241
|
+
blend_lightest(destPixels[destOffset + x],
|
2242
|
+
srcBuffer[sY + (sX >> PRECISIONB)]);
|
2243
|
+
sX += dx;
|
2244
|
+
}
|
2245
|
+
destOffset += screenW;
|
2246
|
+
srcYOffset += dy;
|
2247
|
+
}
|
2248
|
+
break;
|
2249
|
+
|
2250
|
+
case DARKEST:
|
2251
|
+
for (int y = 0; y < destH; y++) {
|
2252
|
+
sX = srcXOffset;
|
2253
|
+
sY = (srcYOffset >> PRECISIONB) * img.pixelWidth;
|
2254
|
+
for (int x = 0; x < destW; x++) {
|
2255
|
+
destPixels[destOffset + x] =
|
2256
|
+
blend_darkest(destPixels[destOffset + x],
|
2257
|
+
srcBuffer[sY + (sX >> PRECISIONB)]);
|
2258
|
+
sX += dx;
|
2259
|
+
}
|
2260
|
+
destOffset += screenW;
|
2261
|
+
srcYOffset += dy;
|
2262
|
+
}
|
2263
|
+
break;
|
2264
|
+
|
2265
|
+
case REPLACE:
|
2266
|
+
for (int y = 0; y < destH; y++) {
|
2267
|
+
sX = srcXOffset;
|
2268
|
+
sY = (srcYOffset >> PRECISIONB) * img.pixelWidth;
|
2269
|
+
for (int x = 0; x < destW; x++) {
|
2270
|
+
destPixels[destOffset + x] = srcBuffer[sY + (sX >> PRECISIONB)];
|
2271
|
+
sX += dx;
|
2272
|
+
}
|
2273
|
+
destOffset += screenW;
|
2274
|
+
srcYOffset += dy;
|
2275
|
+
}
|
2276
|
+
break;
|
2277
|
+
|
2278
|
+
case DIFFERENCE:
|
2279
|
+
for (int y = 0; y < destH; y++) {
|
2280
|
+
sX = srcXOffset;
|
2281
|
+
sY = (srcYOffset >> PRECISIONB) * img.pixelWidth;
|
2282
|
+
for (int x = 0; x < destW; x++) {
|
2283
|
+
destPixels[destOffset + x] =
|
2284
|
+
blend_difference(destPixels[destOffset + x],
|
2285
|
+
srcBuffer[sY + (sX >> PRECISIONB)]);
|
2286
|
+
sX += dx;
|
2287
|
+
}
|
2288
|
+
destOffset += screenW;
|
2289
|
+
srcYOffset += dy;
|
2290
|
+
}
|
2291
|
+
break;
|
2292
|
+
|
2293
|
+
case EXCLUSION:
|
2294
|
+
for (int y = 0; y < destH; y++) {
|
2295
|
+
sX = srcXOffset;
|
2296
|
+
sY = (srcYOffset >> PRECISIONB) * img.pixelWidth;
|
2297
|
+
for (int x = 0; x < destW; x++) {
|
2298
|
+
destPixels[destOffset + x] =
|
2299
|
+
blend_exclusion(destPixels[destOffset + x],
|
2300
|
+
srcBuffer[sY + (sX >> PRECISIONB)]);
|
2301
|
+
sX += dx;
|
2302
|
+
}
|
2303
|
+
destOffset += screenW;
|
2304
|
+
srcYOffset += dy;
|
2305
|
+
}
|
2306
|
+
break;
|
2307
|
+
|
2308
|
+
case MULTIPLY:
|
2309
|
+
for (int y = 0; y < destH; y++) {
|
2310
|
+
sX = srcXOffset;
|
2311
|
+
sY = (srcYOffset >> PRECISIONB) * img.pixelWidth;
|
2312
|
+
for (int x = 0; x < destW; x++) {
|
2313
|
+
destPixels[destOffset + x] =
|
2314
|
+
blend_multiply(destPixels[destOffset + x],
|
2315
|
+
srcBuffer[sY + (sX >> PRECISIONB)]);
|
2316
|
+
sX += dx;
|
2317
|
+
}
|
2318
|
+
destOffset += screenW;
|
2319
|
+
srcYOffset += dy;
|
2320
|
+
}
|
2321
|
+
break;
|
2322
|
+
|
2323
|
+
case SCREEN:
|
2324
|
+
for (int y = 0; y < destH; y++) {
|
2325
|
+
sX = srcXOffset;
|
2326
|
+
sY = (srcYOffset >> PRECISIONB) * img.pixelWidth;
|
2327
|
+
for (int x = 0; x < destW; x++) {
|
2328
|
+
destPixels[destOffset + x] =
|
2329
|
+
blend_screen(destPixels[destOffset + x],
|
2330
|
+
srcBuffer[sY + (sX >> PRECISIONB)]);
|
2331
|
+
sX += dx;
|
2332
|
+
}
|
2333
|
+
destOffset += screenW;
|
2334
|
+
srcYOffset += dy;
|
2335
|
+
}
|
2336
|
+
break;
|
2337
|
+
|
2338
|
+
case OVERLAY:
|
2339
|
+
for (int y = 0; y < destH; y++) {
|
2340
|
+
sX = srcXOffset;
|
2341
|
+
sY = (srcYOffset >> PRECISIONB) * img.pixelWidth;
|
2342
|
+
for (int x = 0; x < destW; x++) {
|
2343
|
+
destPixels[destOffset + x] =
|
2344
|
+
blend_overlay(destPixels[destOffset + x],
|
2345
|
+
srcBuffer[sY + (sX >> PRECISIONB)]);
|
2346
|
+
sX += dx;
|
2347
|
+
}
|
2348
|
+
destOffset += screenW;
|
2349
|
+
srcYOffset += dy;
|
2350
|
+
}
|
2351
|
+
break;
|
2352
|
+
|
2353
|
+
case HARD_LIGHT:
|
2354
|
+
for (int y = 0; y < destH; y++) {
|
2355
|
+
sX = srcXOffset;
|
2356
|
+
sY = (srcYOffset >> PRECISIONB) * img.pixelWidth;
|
2357
|
+
for (int x = 0; x < destW; x++) {
|
2358
|
+
destPixels[destOffset + x] =
|
2359
|
+
blend_hard_light(destPixels[destOffset + x],
|
2360
|
+
srcBuffer[sY + (sX >> PRECISIONB)]);
|
2361
|
+
sX += dx;
|
2362
|
+
}
|
2363
|
+
destOffset += screenW;
|
2364
|
+
srcYOffset += dy;
|
2365
|
+
}
|
2366
|
+
break;
|
2367
|
+
|
2368
|
+
case SOFT_LIGHT:
|
2369
|
+
for (int y = 0; y < destH; y++) {
|
2370
|
+
sX = srcXOffset;
|
2371
|
+
sY = (srcYOffset >> PRECISIONB) * img.pixelWidth;
|
2372
|
+
for (int x = 0; x < destW; x++) {
|
2373
|
+
destPixels[destOffset + x] =
|
2374
|
+
blend_soft_light(destPixels[destOffset + x],
|
2375
|
+
srcBuffer[sY + (sX >> PRECISIONB)]);
|
2376
|
+
sX += dx;
|
2377
|
+
}
|
2378
|
+
destOffset += screenW;
|
2379
|
+
srcYOffset += dy;
|
2380
|
+
}
|
2381
|
+
break;
|
2382
|
+
|
2383
|
+
// davbol - proposed 2007-01-09
|
2384
|
+
case DODGE:
|
2385
|
+
for (int y = 0; y < destH; y++) {
|
2386
|
+
sX = srcXOffset;
|
2387
|
+
sY = (srcYOffset >> PRECISIONB) * img.pixelWidth;
|
2388
|
+
for (int x = 0; x < destW; x++) {
|
2389
|
+
destPixels[destOffset + x] =
|
2390
|
+
blend_dodge(destPixels[destOffset + x],
|
2391
|
+
srcBuffer[sY + (sX >> PRECISIONB)]);
|
2392
|
+
sX += dx;
|
2393
|
+
}
|
2394
|
+
destOffset += screenW;
|
2395
|
+
srcYOffset += dy;
|
2396
|
+
}
|
2397
|
+
break;
|
2398
|
+
|
2399
|
+
case BURN:
|
2400
|
+
for (int y = 0; y < destH; y++) {
|
2401
|
+
sX = srcXOffset;
|
2402
|
+
sY = (srcYOffset >> PRECISIONB) * img.pixelWidth;
|
2403
|
+
for (int x = 0; x < destW; x++) {
|
2404
|
+
destPixels[destOffset + x] =
|
2405
|
+
blend_burn(destPixels[destOffset + x],
|
2406
|
+
srcBuffer[sY + (sX >> PRECISIONB)]);
|
2407
|
+
sX += dx;
|
2408
|
+
}
|
2409
|
+
destOffset += screenW;
|
2410
|
+
srcYOffset += dy;
|
2411
|
+
}
|
2412
|
+
break;
|
2413
|
+
|
2414
|
+
}
|
2415
|
+
}
|
2416
|
+
}
|
2417
|
+
|
2418
|
+
|
2419
|
+
private void filter_new_scanline() {
|
2420
|
+
sX = srcXOffset;
|
2421
|
+
fracV = srcYOffset & PREC_MAXVAL;
|
2422
|
+
ifV = PREC_MAXVAL - fracV + 1;
|
2423
|
+
v1 = (srcYOffset >> PRECISIONB) * iw;
|
2424
|
+
v2 = min((srcYOffset >> PRECISIONB) + 1, ih1) * iw;
|
2425
|
+
}
|
2426
|
+
|
2427
|
+
|
2428
|
+
private int filter_bilinear() {
|
2429
|
+
fracU = sX & PREC_MAXVAL;
|
2430
|
+
ifU = PREC_MAXVAL - fracU + 1;
|
2431
|
+
ul = (ifU * ifV) >> PRECISIONB;
|
2432
|
+
ll = ifU - ul;
|
2433
|
+
ur = ifV - ul;
|
2434
|
+
lr = PREC_MAXVAL + 1 - ul - ll - ur;
|
2435
|
+
u1 = (sX >> PRECISIONB);
|
2436
|
+
u2 = min(u1 + 1, iw1);
|
2437
|
+
|
2438
|
+
// get color values of the 4 neighbouring texels
|
2439
|
+
cUL = srcBuffer[v1 + u1];
|
2440
|
+
cUR = srcBuffer[v1 + u2];
|
2441
|
+
cLL = srcBuffer[v2 + u1];
|
2442
|
+
cLR = srcBuffer[v2 + u2];
|
2443
|
+
|
2444
|
+
r = ((ul*((cUL&RED_MASK)>>16) + ll*((cLL&RED_MASK)>>16) +
|
2445
|
+
ur*((cUR&RED_MASK)>>16) + lr*((cLR&RED_MASK)>>16))
|
2446
|
+
<< PREC_RED_SHIFT) & RED_MASK;
|
2447
|
+
|
2448
|
+
g = ((ul*(cUL&GREEN_MASK) + ll*(cLL&GREEN_MASK) +
|
2449
|
+
ur*(cUR&GREEN_MASK) + lr*(cLR&GREEN_MASK))
|
2450
|
+
>>> PRECISIONB) & GREEN_MASK;
|
2451
|
+
|
2452
|
+
b = (ul*(cUL&BLUE_MASK) + ll*(cLL&BLUE_MASK) +
|
2453
|
+
ur*(cUR&BLUE_MASK) + lr*(cLR&BLUE_MASK))
|
2454
|
+
>>> PRECISIONB;
|
2455
|
+
|
2456
|
+
a = ((ul*((cUL&ALPHA_MASK)>>>24) + ll*((cLL&ALPHA_MASK)>>>24) +
|
2457
|
+
ur*((cUR&ALPHA_MASK)>>>24) + lr*((cLR&ALPHA_MASK)>>>24))
|
2458
|
+
<< PREC_ALPHA_SHIFT) & ALPHA_MASK;
|
2459
|
+
|
2460
|
+
return a | r | g | b;
|
2461
|
+
}
|
2462
|
+
|
2463
|
+
|
2464
|
+
|
2465
|
+
//////////////////////////////////////////////////////////////
|
2466
|
+
|
2467
|
+
// internal blending methods
|
2468
|
+
|
2469
|
+
|
2470
|
+
private static int min(int a, int b) {
|
2471
|
+
return (a < b) ? a : b;
|
2472
|
+
}
|
2473
|
+
|
2474
|
+
|
2475
|
+
private static int max(int a, int b) {
|
2476
|
+
return (a > b) ? a : b;
|
2477
|
+
}
|
2478
|
+
|
2479
|
+
|
2480
|
+
/////////////////////////////////////////////////////////////
|
2481
|
+
|
2482
|
+
// BLEND MODE IMPLEMENTATIONS
|
2483
|
+
|
2484
|
+
/*
|
2485
|
+
* Jakub Valtar
|
2486
|
+
*
|
2487
|
+
* All modes use SRC alpha to interpolate between DST and the result of
|
2488
|
+
* the operation:
|
2489
|
+
*
|
2490
|
+
* R = (1 - SRC_ALPHA) * DST + SRC_ALPHA * <RESULT OF THE OPERATION>
|
2491
|
+
*
|
2492
|
+
* Comments above each mode only specify the formula of its operation.
|
2493
|
+
*
|
2494
|
+
* These implementations treat alpha 127 (=255/2) as a perfect 50 % mix.
|
2495
|
+
*
|
2496
|
+
* One alpha value between 126 and 127 is intentionally left out,
|
2497
|
+
* so the step 126 -> 127 is twice as big compared to other steps.
|
2498
|
+
* This is because our colors are in 0..255 range, but we divide
|
2499
|
+
* by right shifting 8 places (=256) which is much faster than
|
2500
|
+
* (correct) float division by 255.0f. The missing value was placed
|
2501
|
+
* between 126 and 127, because limits of the range (near 0 and 255) and
|
2502
|
+
* the middle value (127) have to blend correctly.
|
2503
|
+
*
|
2504
|
+
* Below you will often see RED and BLUE channels (RB) manipulated together
|
2505
|
+
* and GREEN channel (GN) manipulated separately. It is sometimes possible
|
2506
|
+
* because the operation won't use more than 16 bits, so we process the RED
|
2507
|
+
* channel in the upper 16 bits and BLUE channel in the lower 16 bits. This
|
2508
|
+
* decreases the number of operations per pixel and thus makes things faster.
|
2509
|
+
*
|
2510
|
+
* Some of the modes are hand tweaked (various +1s etc.) to be more accurate
|
2511
|
+
* and to produce correct values in extremes. Below is a sketch you can use
|
2512
|
+
* to check any blending function for
|
2513
|
+
*
|
2514
|
+
* 1) Discrepancies between color channels:
|
2515
|
+
* - highlighted by the offending color
|
2516
|
+
* 2) Behavior at extremes (set colorCount to 256):
|
2517
|
+
* - values of all corners are printed to the console
|
2518
|
+
* 3) Rounding errors:
|
2519
|
+
* - set colorCount to lower value to better see color bands
|
2520
|
+
*
|
2521
|
+
|
2522
|
+
// use powers of 2 in range 2..256
|
2523
|
+
// to better see color bands
|
2524
|
+
final int colorCount = 256;
|
2525
|
+
|
2526
|
+
final int blockSize = 3;
|
2527
|
+
|
2528
|
+
void settings() {
|
2529
|
+
size(blockSize * 256, blockSize * 256);
|
2530
|
+
}
|
2531
|
+
|
2532
|
+
void setup() { }
|
2533
|
+
|
2534
|
+
void draw() {
|
2535
|
+
noStroke();
|
2536
|
+
colorMode(RGB, colorCount-1);
|
2537
|
+
int alpha = (mouseX / blockSize) << 24;
|
2538
|
+
int r, g, b, r2, g2, b2 = 0;
|
2539
|
+
for (int x = 0; x <= 0xFF; x++) {
|
2540
|
+
for (int y = 0; y <= 0xFF; y++) {
|
2541
|
+
int dst = (x << 16) | (x << 8) | x;
|
2542
|
+
int src = (y << 16) | (y << 8) | y | alpha;
|
2543
|
+
int result = testFunction(dst, src);
|
2544
|
+
r = r2 = (result >> 16 & 0xFF);
|
2545
|
+
g = g2 = (result >> 8 & 0xFF);
|
2546
|
+
b = b2 = (result >> 0 & 0xFF);
|
2547
|
+
if (r != g && r != b) r2 = (128 + r2) % 255;
|
2548
|
+
if (g != r && g != b) g2 = (128 + g2) % 255;
|
2549
|
+
if (b != r && b != g) b2 = (128 + b2) % 255;
|
2550
|
+
fill(r2 % colorCount, g2 % colorCount, b2 % colorCount);
|
2551
|
+
rect(x * blockSize, y * blockSize, blockSize, blockSize);
|
2552
|
+
}
|
2553
|
+
}
|
2554
|
+
println(
|
2555
|
+
"alpha:", mouseX/blockSize,
|
2556
|
+
"TL:", hex(get(0, 0)),
|
2557
|
+
"TR:", hex(get(width-1, 0)),
|
2558
|
+
"BR:", hex(get(width-1, height-1)),
|
2559
|
+
"BL:", hex(get(0, height-1)));
|
2560
|
+
}
|
2561
|
+
|
2562
|
+
int testFunction(int dst, int src) {
|
2563
|
+
// your function here
|
2564
|
+
return dst;
|
2565
|
+
}
|
2566
|
+
|
2567
|
+
*
|
2568
|
+
*
|
2569
|
+
*/
|
2570
|
+
|
2571
|
+
private static final int RB_MASK = 0x00FF00FF;
|
2572
|
+
private static final int GN_MASK = 0x0000FF00;
|
2573
|
+
|
2574
|
+
/**
|
2575
|
+
* Blend
|
2576
|
+
* O = S
|
2577
|
+
*/
|
2578
|
+
private static int blend_blend(int dst, int src) {
|
2579
|
+
int a = src >>> 24;
|
2580
|
+
|
2581
|
+
int s_a = a + (a >= 0x7F ? 1 : 0);
|
2582
|
+
int d_a = 0x100 - s_a;
|
2583
|
+
|
2584
|
+
return min((dst >>> 24) + a, 0xFF) << 24 |
|
2585
|
+
((dst & RB_MASK) * d_a + (src & RB_MASK) * s_a) >>> 8 & RB_MASK |
|
2586
|
+
((dst & GN_MASK) * d_a + (src & GN_MASK) * s_a) >>> 8 & GN_MASK;
|
2587
|
+
}
|
2588
|
+
|
2589
|
+
|
2590
|
+
/**
|
2591
|
+
* Add
|
2592
|
+
* O = MIN(D + S, 1)
|
2593
|
+
*/
|
2594
|
+
private static int blend_add_pin(int dst, int src) {
|
2595
|
+
int a = src >>> 24;
|
2596
|
+
|
2597
|
+
int s_a = a + (a >= 0x7F ? 1 : 0);
|
2598
|
+
|
2599
|
+
int rb = (dst & RB_MASK) + ((src & RB_MASK) * s_a >>> 8 & RB_MASK);
|
2600
|
+
int gn = (dst & GN_MASK) + ((src & GN_MASK) * s_a >>> 8);
|
2601
|
+
|
2602
|
+
return min((dst >>> 24) + a, 0xFF) << 24 |
|
2603
|
+
min(rb & 0xFFFF0000, RED_MASK) |
|
2604
|
+
min(gn & 0x00FFFF00, GREEN_MASK) |
|
2605
|
+
min(rb & 0x0000FFFF, BLUE_MASK);
|
2606
|
+
}
|
2607
|
+
|
2608
|
+
|
2609
|
+
/**
|
2610
|
+
* Subtract
|
2611
|
+
* O = MAX(0, D - S)
|
2612
|
+
*/
|
2613
|
+
private static int blend_sub_pin(int dst, int src) {
|
2614
|
+
int a = src >>> 24;
|
2615
|
+
|
2616
|
+
int s_a = a + (a >= 0x7F ? 1 : 0);
|
2617
|
+
|
2618
|
+
int rb = ((src & RB_MASK) * s_a >>> 8);
|
2619
|
+
int gn = ((src & GREEN_MASK) * s_a >>> 8);
|
2620
|
+
|
2621
|
+
return min((dst >>> 24) + a, 0xFF) << 24 |
|
2622
|
+
max((dst & RED_MASK) - (rb & RED_MASK), 0) |
|
2623
|
+
max((dst & GREEN_MASK) - (gn & GREEN_MASK), 0) |
|
2624
|
+
max((dst & BLUE_MASK) - (rb & BLUE_MASK), 0);
|
2625
|
+
}
|
2626
|
+
|
2627
|
+
|
2628
|
+
/**
|
2629
|
+
* Lightest
|
2630
|
+
* O = MAX(D, S)
|
2631
|
+
*/
|
2632
|
+
private static int blend_lightest(int dst, int src) {
|
2633
|
+
int a = src >>> 24;
|
2634
|
+
|
2635
|
+
int s_a = a + (a >= 0x7F ? 1 : 0);
|
2636
|
+
int d_a = 0x100 - s_a;
|
2637
|
+
|
2638
|
+
int rb = max(src & RED_MASK, dst & RED_MASK) |
|
2639
|
+
max(src & BLUE_MASK, dst & BLUE_MASK);
|
2640
|
+
int gn = max(src & GREEN_MASK, dst & GREEN_MASK);
|
2641
|
+
|
2642
|
+
return min((dst >>> 24) + a, 0xFF) << 24 |
|
2643
|
+
((dst & RB_MASK) * d_a + rb * s_a) >>> 8 & RB_MASK |
|
2644
|
+
((dst & GN_MASK) * d_a + gn * s_a) >>> 8 & GN_MASK;
|
2645
|
+
}
|
2646
|
+
|
2647
|
+
|
2648
|
+
/**
|
2649
|
+
* Darkest
|
2650
|
+
* O = MIN(D, S)
|
2651
|
+
*/
|
2652
|
+
private static int blend_darkest(int dst, int src) {
|
2653
|
+
int a = src >>> 24;
|
2654
|
+
|
2655
|
+
int s_a = a + (a >= 0x7F ? 1 : 0);
|
2656
|
+
int d_a = 0x100 - s_a;
|
2657
|
+
|
2658
|
+
int rb = min(src & RED_MASK, dst & RED_MASK) |
|
2659
|
+
min(src & BLUE_MASK, dst & BLUE_MASK);
|
2660
|
+
int gn = min(src & GREEN_MASK, dst & GREEN_MASK);
|
2661
|
+
|
2662
|
+
return min((dst >>> 24) + a, 0xFF) << 24 |
|
2663
|
+
((dst & RB_MASK) * d_a + rb * s_a) >>> 8 & RB_MASK |
|
2664
|
+
((dst & GN_MASK) * d_a + gn * s_a) >>> 8 & GN_MASK;
|
2665
|
+
}
|
2666
|
+
|
2667
|
+
|
2668
|
+
/**
|
2669
|
+
* Difference
|
2670
|
+
* O = ABS(D - S)
|
2671
|
+
*/
|
2672
|
+
private static int blend_difference(int dst, int src) {
|
2673
|
+
int a = src >>> 24;
|
2674
|
+
|
2675
|
+
int s_a = a + (a >= 0x7F ? 1 : 0);
|
2676
|
+
int d_a = 0x100 - s_a;
|
2677
|
+
|
2678
|
+
int r = (dst & RED_MASK) - (src & RED_MASK);
|
2679
|
+
int b = (dst & BLUE_MASK) - (src & BLUE_MASK);
|
2680
|
+
int g = (dst & GREEN_MASK) - (src & GREEN_MASK);
|
2681
|
+
|
2682
|
+
int rb = (r < 0 ? -r : r) |
|
2683
|
+
(b < 0 ? -b : b);
|
2684
|
+
int gn = (g < 0 ? -g : g);
|
2685
|
+
|
2686
|
+
return min((dst >>> 24) + a, 0xFF) << 24 |
|
2687
|
+
((dst & RB_MASK) * d_a + rb * s_a) >>> 8 & RB_MASK |
|
2688
|
+
((dst & GN_MASK) * d_a + gn * s_a) >>> 8 & GN_MASK;
|
2689
|
+
}
|
2690
|
+
|
2691
|
+
|
2692
|
+
/**
|
2693
|
+
* Exclusion
|
2694
|
+
* O = (1 - S)D + S(1 - D)
|
2695
|
+
* O = D + S - 2DS
|
2696
|
+
*/
|
2697
|
+
private static int blend_exclusion(int dst, int src) {
|
2698
|
+
int a = src >>> 24;
|
2699
|
+
|
2700
|
+
int s_a = a + (a >= 0x7F ? 1 : 0);
|
2701
|
+
int d_a = 0x100 - s_a;
|
2702
|
+
|
2703
|
+
int d_rb = dst & RB_MASK;
|
2704
|
+
int d_gn = dst & GN_MASK;
|
2705
|
+
|
2706
|
+
int s_gn = src & GN_MASK;
|
2707
|
+
|
2708
|
+
int f_r = (dst & RED_MASK) >> 16;
|
2709
|
+
int f_b = (dst & BLUE_MASK);
|
2710
|
+
|
2711
|
+
int rb_sub =
|
2712
|
+
((src & RED_MASK) * (f_r + (f_r >= 0x7F ? 1 : 0)) |
|
2713
|
+
(src & BLUE_MASK) * (f_b + (f_b >= 0x7F ? 1 : 0)))
|
2714
|
+
>>> 7 & 0x01FF01FF;
|
2715
|
+
int gn_sub = s_gn * (d_gn + (d_gn >= 0x7F00 ? 0x100 : 0))
|
2716
|
+
>>> 15 & 0x0001FF00;
|
2717
|
+
|
2718
|
+
return min((dst >>> 24) + a, 0xFF) << 24 |
|
2719
|
+
(d_rb * d_a + (d_rb + (src & RB_MASK) - rb_sub) * s_a) >>> 8 & RB_MASK |
|
2720
|
+
(d_gn * d_a + (d_gn + s_gn - gn_sub) * s_a) >>> 8 & GN_MASK;
|
2721
|
+
}
|
2722
|
+
|
2723
|
+
|
2724
|
+
/*
|
2725
|
+
* Multiply
|
2726
|
+
* O = DS
|
2727
|
+
*/
|
2728
|
+
private static int blend_multiply(int dst, int src) {
|
2729
|
+
int a = src >>> 24;
|
2730
|
+
|
2731
|
+
int s_a = a + (a >= 0x7F ? 1 : 0);
|
2732
|
+
int d_a = 0x100 - s_a;
|
2733
|
+
|
2734
|
+
int d_gn = dst & GN_MASK;
|
2735
|
+
|
2736
|
+
int f_r = (dst & RED_MASK) >> 16;
|
2737
|
+
int f_b = (dst & BLUE_MASK);
|
2738
|
+
|
2739
|
+
int rb =
|
2740
|
+
((src & RED_MASK) * (f_r + 1) |
|
2741
|
+
(src & BLUE_MASK) * (f_b + 1))
|
2742
|
+
>>> 8 & RB_MASK;
|
2743
|
+
int gn =
|
2744
|
+
(src & GREEN_MASK) * (d_gn + 0x100)
|
2745
|
+
>>> 16 & GN_MASK;
|
2746
|
+
|
2747
|
+
return min((dst >>> 24) + a, 0xFF) << 24 |
|
2748
|
+
((dst & RB_MASK) * d_a + rb * s_a) >>> 8 & RB_MASK |
|
2749
|
+
(d_gn * d_a + gn * s_a) >>> 8 & GN_MASK;
|
2750
|
+
}
|
2751
|
+
|
2752
|
+
|
2753
|
+
/**
|
2754
|
+
* Screen
|
2755
|
+
* O = 1 - (1 - D)(1 - S)
|
2756
|
+
* O = D + S - DS
|
2757
|
+
*/
|
2758
|
+
private static int blend_screen(int dst, int src) {
|
2759
|
+
int a = src >>> 24;
|
2760
|
+
|
2761
|
+
int s_a = a + (a >= 0x7F ? 1 : 0);
|
2762
|
+
int d_a = 0x100 - s_a;
|
2763
|
+
|
2764
|
+
int d_rb = dst & RB_MASK;
|
2765
|
+
int d_gn = dst & GN_MASK;
|
2766
|
+
|
2767
|
+
int s_gn = src & GN_MASK;
|
2768
|
+
|
2769
|
+
int f_r = (dst & RED_MASK) >> 16;
|
2770
|
+
int f_b = (dst & BLUE_MASK);
|
2771
|
+
|
2772
|
+
int rb_sub =
|
2773
|
+
((src & RED_MASK) * (f_r + 1) |
|
2774
|
+
(src & BLUE_MASK) * (f_b + 1))
|
2775
|
+
>>> 8 & RB_MASK;
|
2776
|
+
int gn_sub = s_gn * (d_gn + 0x100)
|
2777
|
+
>>> 16 & GN_MASK;
|
2778
|
+
|
2779
|
+
return min((dst >>> 24) + a, 0xFF) << 24 |
|
2780
|
+
(d_rb * d_a + (d_rb + (src & RB_MASK) - rb_sub) * s_a) >>> 8 & RB_MASK |
|
2781
|
+
(d_gn * d_a + (d_gn + s_gn - gn_sub) * s_a) >>> 8 & GN_MASK;
|
2782
|
+
}
|
2783
|
+
|
2784
|
+
|
2785
|
+
/**
|
2786
|
+
* Overlay
|
2787
|
+
* O = 2 * MULTIPLY(D, S) = 2DS for D < 0.5
|
2788
|
+
* O = 2 * SCREEN(D, S) - 1 = 2(S + D - DS) - 1 otherwise
|
2789
|
+
*/
|
2790
|
+
private static int blend_overlay(int dst, int src) {
|
2791
|
+
int a = src >>> 24;
|
2792
|
+
|
2793
|
+
int s_a = a + (a >= 0x7F ? 1 : 0);
|
2794
|
+
int d_a = 0x100 - s_a;
|
2795
|
+
|
2796
|
+
int d_r = dst & RED_MASK;
|
2797
|
+
int d_g = dst & GREEN_MASK;
|
2798
|
+
int d_b = dst & BLUE_MASK;
|
2799
|
+
|
2800
|
+
int s_r = src & RED_MASK;
|
2801
|
+
int s_g = src & GREEN_MASK;
|
2802
|
+
int s_b = src & BLUE_MASK;
|
2803
|
+
|
2804
|
+
int r = (d_r < 0x800000) ?
|
2805
|
+
d_r * ((s_r >>> 16) + 1) >>> 7 :
|
2806
|
+
0xFF0000 - ((0x100 - (s_r >>> 16)) * (RED_MASK - d_r) >>> 7);
|
2807
|
+
int g = (d_g < 0x8000) ?
|
2808
|
+
d_g * (s_g + 0x100) >>> 15 :
|
2809
|
+
(0xFF00 - ((0x10000 - s_g) * (GREEN_MASK - d_g) >>> 15));
|
2810
|
+
int b = (d_b < 0x80) ?
|
2811
|
+
d_b * (s_b + 1) >>> 7 :
|
2812
|
+
(0xFF00 - ((0x100 - s_b) * (BLUE_MASK - d_b) << 1)) >>> 8;
|
2813
|
+
|
2814
|
+
return min((dst >>> 24) + a, 0xFF) << 24 |
|
2815
|
+
((dst & RB_MASK) * d_a + ((r | b) & RB_MASK) * s_a) >>> 8 & RB_MASK |
|
2816
|
+
((dst & GN_MASK) * d_a + (g & GN_MASK) * s_a) >>> 8 & GN_MASK;
|
2817
|
+
}
|
2818
|
+
|
2819
|
+
|
2820
|
+
/**
|
2821
|
+
* Hard Light
|
2822
|
+
* O = OVERLAY(S, D)
|
2823
|
+
*
|
2824
|
+
* O = 2 * MULTIPLY(D, S) = 2DS for S < 0.5
|
2825
|
+
* O = 2 * SCREEN(D, S) - 1 = 2(S + D - DS) - 1 otherwise
|
2826
|
+
*/
|
2827
|
+
private static int blend_hard_light(int dst, int src) {
|
2828
|
+
int a = src >>> 24;
|
2829
|
+
|
2830
|
+
int s_a = a + (a >= 0x7F ? 1 : 0);
|
2831
|
+
int d_a = 0x100 - s_a;
|
2832
|
+
|
2833
|
+
int d_r = dst & RED_MASK;
|
2834
|
+
int d_g = dst & GREEN_MASK;
|
2835
|
+
int d_b = dst & BLUE_MASK;
|
2836
|
+
|
2837
|
+
int s_r = src & RED_MASK;
|
2838
|
+
int s_g = src & GREEN_MASK;
|
2839
|
+
int s_b = src & BLUE_MASK;
|
2840
|
+
|
2841
|
+
int r = (s_r < 0x800000) ?
|
2842
|
+
s_r * ((d_r >>> 16) + 1) >>> 7 :
|
2843
|
+
0xFF0000 - ((0x100 - (d_r >>> 16)) * (RED_MASK - s_r) >>> 7);
|
2844
|
+
int g = (s_g < 0x8000) ?
|
2845
|
+
s_g * (d_g + 0x100) >>> 15 :
|
2846
|
+
(0xFF00 - ((0x10000 - d_g) * (GREEN_MASK - s_g) >>> 15));
|
2847
|
+
int b = (s_b < 0x80) ?
|
2848
|
+
s_b * (d_b + 1) >>> 7 :
|
2849
|
+
(0xFF00 - ((0x100 - d_b) * (BLUE_MASK - s_b) << 1)) >>> 8;
|
2850
|
+
|
2851
|
+
return min((dst >>> 24) + a, 0xFF) << 24 |
|
2852
|
+
((dst & RB_MASK) * d_a + ((r | b) & RB_MASK) * s_a) >>> 8 & RB_MASK |
|
2853
|
+
((dst & GN_MASK) * d_a + (g & GN_MASK) * s_a) >>> 8 & GN_MASK;
|
2854
|
+
}
|
2855
|
+
|
2856
|
+
|
2857
|
+
/**
|
2858
|
+
* Soft Light (Pegtop)
|
2859
|
+
* O = (1 - D) * MULTIPLY(D, S) + D * SCREEN(D, S)
|
2860
|
+
* O = (1 - D) * DS + D * (1 - (1 - D)(1 - S))
|
2861
|
+
* O = 2DS + DD - 2DDS
|
2862
|
+
*/
|
2863
|
+
private static int blend_soft_light(int dst, int src) {
|
2864
|
+
int a = src >>> 24;
|
2865
|
+
|
2866
|
+
int s_a = a + (a >= 0x7F ? 1 : 0);
|
2867
|
+
int d_a = 0x100 - s_a;
|
2868
|
+
|
2869
|
+
int d_r = dst & RED_MASK;
|
2870
|
+
int d_g = dst & GREEN_MASK;
|
2871
|
+
int d_b = dst & BLUE_MASK;
|
2872
|
+
|
2873
|
+
int s_r1 = src & RED_MASK >> 16;
|
2874
|
+
int s_g1 = src & GREEN_MASK >> 8;
|
2875
|
+
int s_b1 = src & BLUE_MASK;
|
2876
|
+
|
2877
|
+
int d_r1 = (d_r >> 16) + (s_r1 < 7F ? 1 : 0);
|
2878
|
+
int d_g1 = (d_g >> 8) + (s_g1 < 7F ? 1 : 0);
|
2879
|
+
int d_b1 = d_b + (s_b1 < 7F ? 1 : 0);
|
2880
|
+
|
2881
|
+
int r = (s_r1 * d_r >> 7) + 0xFF * d_r1 * (d_r1 + 1) -
|
2882
|
+
((s_r1 * d_r1 * d_r1) << 1) & RED_MASK;
|
2883
|
+
int g = (s_g1 * d_g << 1) + 0xFF * d_g1 * (d_g1 + 1) -
|
2884
|
+
((s_g1 * d_g1 * d_g1) << 1) >>> 8 & GREEN_MASK;
|
2885
|
+
int b = (s_b1 * d_b << 9) + 0xFF * d_b1 * (d_b1 + 1) -
|
2886
|
+
((s_b1 * d_b1 * d_b1) << 1) >>> 16;
|
2887
|
+
|
2888
|
+
return min((dst >>> 24) + a, 0xFF) << 24 |
|
2889
|
+
((dst & RB_MASK) * d_a + (r | b) * s_a) >>> 8 & RB_MASK |
|
2890
|
+
((dst & GN_MASK) * d_a + g * s_a) >>> 8 & GN_MASK;
|
2891
|
+
}
|
2892
|
+
|
2893
|
+
|
2894
|
+
/**
|
2895
|
+
* Dodge
|
2896
|
+
* O = D / (1 - S)
|
2897
|
+
*/
|
2898
|
+
private static int blend_dodge(int dst, int src) {
|
2899
|
+
int a = src >>> 24;
|
2900
|
+
|
2901
|
+
int s_a = a + (a >= 0x7F ? 1 : 0);
|
2902
|
+
int d_a = 0x100 - s_a;
|
2903
|
+
|
2904
|
+
int r = (dst & RED_MASK) / (256 - ((src & RED_MASK) >> 16));
|
2905
|
+
int g = ((dst & GREEN_MASK) << 8) / (256 - ((src & GREEN_MASK) >> 8));
|
2906
|
+
int b = ((dst & BLUE_MASK) << 8) / (256 - (src & BLUE_MASK));
|
2907
|
+
|
2908
|
+
int rb =
|
2909
|
+
(r > 0xFF00 ? 0xFF0000 : ((r << 8) & RED_MASK)) |
|
2910
|
+
(b > 0x00FF ? 0x0000FF : b);
|
2911
|
+
int gn =
|
2912
|
+
(g > 0xFF00 ? 0x00FF00 : (g & GREEN_MASK));
|
2913
|
+
|
2914
|
+
return min((dst >>> 24) + a, 0xFF) << 24 |
|
2915
|
+
((dst & RB_MASK) * d_a + rb * s_a) >>> 8 & RB_MASK |
|
2916
|
+
((dst & GN_MASK) * d_a + gn * s_a) >>> 8 & GN_MASK;
|
2917
|
+
}
|
2918
|
+
|
2919
|
+
|
2920
|
+
/**
|
2921
|
+
* Burn
|
2922
|
+
* O = 1 - (1 - A) / B
|
2923
|
+
*/
|
2924
|
+
private static int blend_burn(int dst, int src) {
|
2925
|
+
int a = src >>> 24;
|
2926
|
+
|
2927
|
+
int s_a = a + (a >= 0x7F ? 1 : 0);
|
2928
|
+
int d_a = 0x100 - s_a;
|
2929
|
+
|
2930
|
+
int r = ((0xFF0000 - (dst & RED_MASK))) / (1 + (src & RED_MASK >> 16));
|
2931
|
+
int g = ((0x00FF00 - (dst & GREEN_MASK)) << 8) / (1 + (src & GREEN_MASK >> 8));
|
2932
|
+
int b = ((0x0000FF - (dst & BLUE_MASK)) << 8) / (1 + (src & BLUE_MASK));
|
2933
|
+
|
2934
|
+
int rb = RB_MASK -
|
2935
|
+
(r > 0xFF00 ? 0xFF0000 : ((r << 8) & RED_MASK)) -
|
2936
|
+
(b > 0x00FF ? 0x0000FF : b);
|
2937
|
+
int gn = GN_MASK -
|
2938
|
+
(g > 0xFF00 ? 0x00FF00 : (g & GREEN_MASK));
|
2939
|
+
|
2940
|
+
return min((dst >>> 24) + a, 0xFF) << 24 |
|
2941
|
+
((dst & RB_MASK) * d_a + rb * s_a) >>> 8 & RB_MASK |
|
2942
|
+
((dst & GN_MASK) * d_a + gn * s_a) >>> 8 & GN_MASK;
|
2943
|
+
}
|
2944
|
+
|
2945
|
+
|
2946
|
+
//////////////////////////////////////////////////////////////
|
2947
|
+
|
2948
|
+
// FILE I/O
|
2949
|
+
|
2950
|
+
|
2951
|
+
static byte TIFF_HEADER[] = {
|
2952
|
+
77, 77, 0, 42, 0, 0, 0, 8, 0, 9, 0, -2, 0, 4, 0, 0, 0, 1, 0, 0,
|
2953
|
+
0, 0, 1, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 3, 0, 0, 0, 1,
|
2954
|
+
0, 0, 0, 0, 1, 2, 0, 3, 0, 0, 0, 3, 0, 0, 0, 122, 1, 6, 0, 3, 0,
|
2955
|
+
0, 0, 1, 0, 2, 0, 0, 1, 17, 0, 4, 0, 0, 0, 1, 0, 0, 3, 0, 1, 21,
|
2956
|
+
0, 3, 0, 0, 0, 1, 0, 3, 0, 0, 1, 22, 0, 3, 0, 0, 0, 1, 0, 0, 0, 0,
|
2957
|
+
1, 23, 0, 4, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 8, 0, 8
|
2958
|
+
};
|
2959
|
+
|
2960
|
+
|
2961
|
+
static final String TIFF_ERROR =
|
2962
|
+
"Error: Processing can only read its own TIFF files.";
|
2963
|
+
|
2964
|
+
static protected PImage loadTIFF(byte tiff[]) {
|
2965
|
+
if ((tiff[42] != tiff[102]) || // width/height in both places
|
2966
|
+
(tiff[43] != tiff[103])) {
|
2967
|
+
System.err.println(TIFF_ERROR);
|
2968
|
+
return null;
|
2969
|
+
}
|
2970
|
+
|
2971
|
+
int width =
|
2972
|
+
((tiff[30] & 0xff) << 8) | (tiff[31] & 0xff);
|
2973
|
+
int height =
|
2974
|
+
((tiff[42] & 0xff) << 8) | (tiff[43] & 0xff);
|
2975
|
+
|
2976
|
+
int count =
|
2977
|
+
((tiff[114] & 0xff) << 24) |
|
2978
|
+
((tiff[115] & 0xff) << 16) |
|
2979
|
+
((tiff[116] & 0xff) << 8) |
|
2980
|
+
(tiff[117] & 0xff);
|
2981
|
+
if (count != width * height * 3) {
|
2982
|
+
System.err.println(TIFF_ERROR + " (" + width + ", " + height +")");
|
2983
|
+
return null;
|
2984
|
+
}
|
2985
|
+
|
2986
|
+
// check the rest of the header
|
2987
|
+
for (int i = 0; i < TIFF_HEADER.length; i++) {
|
2988
|
+
if ((i == 30) || (i == 31) || (i == 42) || (i == 43) ||
|
2989
|
+
(i == 102) || (i == 103) ||
|
2990
|
+
(i == 114) || (i == 115) || (i == 116) || (i == 117)) continue;
|
2991
|
+
|
2992
|
+
if (tiff[i] != TIFF_HEADER[i]) {
|
2993
|
+
System.err.println(TIFF_ERROR + " (" + i + ")");
|
2994
|
+
return null;
|
2995
|
+
}
|
2996
|
+
}
|
2997
|
+
|
2998
|
+
PImage outgoing = new PImage(width, height, RGB);
|
2999
|
+
int index = 768;
|
3000
|
+
count /= 3;
|
3001
|
+
for (int i = 0; i < count; i++) {
|
3002
|
+
outgoing.pixels[i] =
|
3003
|
+
0xFF000000 |
|
3004
|
+
(tiff[index++] & 0xff) << 16 |
|
3005
|
+
(tiff[index++] & 0xff) << 8 |
|
3006
|
+
(tiff[index++] & 0xff);
|
3007
|
+
}
|
3008
|
+
return outgoing;
|
3009
|
+
}
|
3010
|
+
|
3011
|
+
|
3012
|
+
protected boolean saveTIFF(OutputStream output) {
|
3013
|
+
// shutting off the warning, people can figure this out themselves
|
3014
|
+
/*
|
3015
|
+
if (format != RGB) {
|
3016
|
+
System.err.println("Warning: only RGB information is saved with " +
|
3017
|
+
".tif files. Use .tga or .png for ARGB images and others.");
|
3018
|
+
}
|
3019
|
+
*/
|
3020
|
+
try {
|
3021
|
+
byte tiff[] = new byte[768];
|
3022
|
+
System.arraycopy(TIFF_HEADER, 0, tiff, 0, TIFF_HEADER.length);
|
3023
|
+
|
3024
|
+
tiff[30] = (byte) ((pixelWidth >> 8) & 0xff);
|
3025
|
+
tiff[31] = (byte) ((pixelWidth) & 0xff);
|
3026
|
+
tiff[42] = tiff[102] = (byte) ((pixelHeight >> 8) & 0xff);
|
3027
|
+
tiff[43] = tiff[103] = (byte) ((pixelHeight) & 0xff);
|
3028
|
+
|
3029
|
+
int count = pixelWidth*pixelHeight*3;
|
3030
|
+
tiff[114] = (byte) ((count >> 24) & 0xff);
|
3031
|
+
tiff[115] = (byte) ((count >> 16) & 0xff);
|
3032
|
+
tiff[116] = (byte) ((count >> 8) & 0xff);
|
3033
|
+
tiff[117] = (byte) ((count) & 0xff);
|
3034
|
+
|
3035
|
+
// spew the header to the disk
|
3036
|
+
output.write(tiff);
|
3037
|
+
|
3038
|
+
for (int i = 0; i < pixels.length; i++) {
|
3039
|
+
output.write((pixels[i] >> 16) & 0xff);
|
3040
|
+
output.write((pixels[i] >> 8) & 0xff);
|
3041
|
+
output.write(pixels[i] & 0xff);
|
3042
|
+
}
|
3043
|
+
output.flush();
|
3044
|
+
return true;
|
3045
|
+
|
3046
|
+
} catch (IOException e) {
|
3047
|
+
e.printStackTrace();
|
3048
|
+
}
|
3049
|
+
return false;
|
3050
|
+
}
|
3051
|
+
|
3052
|
+
|
3053
|
+
/**
|
3054
|
+
* Creates a Targa32 formatted byte sequence of specified
|
3055
|
+
* pixel buffer using RLE compression.
|
3056
|
+
* </p>
|
3057
|
+
* Also figured out how to avoid parsing the image upside-down
|
3058
|
+
* (there's a header flag to set the image origin to top-left)
|
3059
|
+
* </p>
|
3060
|
+
* Starting with revision 0092, the format setting is taken into account:
|
3061
|
+
* <UL>
|
3062
|
+
* <LI><TT>ALPHA</TT> images written as 8bit grayscale (uses lowest byte)
|
3063
|
+
* <LI><TT>RGB</TT> → 24 bits
|
3064
|
+
* <LI><TT>ARGB</TT> → 32 bits
|
3065
|
+
* </UL>
|
3066
|
+
* All versions are RLE compressed.
|
3067
|
+
* </p>
|
3068
|
+
* Contributed by toxi 8-10 May 2005, based on this RLE
|
3069
|
+
* <A HREF="http://www.wotsit.org/download.asp?f=tga">specification</A>
|
3070
|
+
*/
|
3071
|
+
protected boolean saveTGA(OutputStream output) {
|
3072
|
+
byte header[] = new byte[18];
|
3073
|
+
|
3074
|
+
if (format == ALPHA) { // save ALPHA images as 8bit grayscale
|
3075
|
+
header[2] = 0x0B;
|
3076
|
+
header[16] = 0x08;
|
3077
|
+
header[17] = 0x28;
|
3078
|
+
|
3079
|
+
} else if (format == RGB) {
|
3080
|
+
header[2] = 0x0A;
|
3081
|
+
header[16] = 24;
|
3082
|
+
header[17] = 0x20;
|
3083
|
+
|
3084
|
+
} else if (format == ARGB) {
|
3085
|
+
header[2] = 0x0A;
|
3086
|
+
header[16] = 32;
|
3087
|
+
header[17] = 0x28;
|
3088
|
+
|
3089
|
+
} else {
|
3090
|
+
throw new RuntimeException("Image format not recognized inside save()");
|
3091
|
+
}
|
3092
|
+
// set image dimensions lo-hi byte order
|
3093
|
+
header[12] = (byte) (pixelWidth & 0xff);
|
3094
|
+
header[13] = (byte) (pixelWidth >> 8);
|
3095
|
+
header[14] = (byte) (pixelHeight & 0xff);
|
3096
|
+
header[15] = (byte) (pixelHeight >> 8);
|
3097
|
+
|
3098
|
+
try {
|
3099
|
+
output.write(header);
|
3100
|
+
|
3101
|
+
int maxLen = pixelHeight * pixelWidth;
|
3102
|
+
int index = 0;
|
3103
|
+
int col; //, prevCol;
|
3104
|
+
int[] currChunk = new int[128];
|
3105
|
+
|
3106
|
+
// 8bit image exporter is in separate loop
|
3107
|
+
// to avoid excessive conditionals...
|
3108
|
+
if (format == ALPHA) {
|
3109
|
+
while (index < maxLen) {
|
3110
|
+
boolean isRLE = false;
|
3111
|
+
int rle = 1;
|
3112
|
+
currChunk[0] = col = pixels[index] & 0xff;
|
3113
|
+
while (index + rle < maxLen) {
|
3114
|
+
if (col != (pixels[index + rle]&0xff) || rle == 128) {
|
3115
|
+
isRLE = (rle > 1);
|
3116
|
+
break;
|
3117
|
+
}
|
3118
|
+
rle++;
|
3119
|
+
}
|
3120
|
+
if (isRLE) {
|
3121
|
+
output.write(0x80 | (rle - 1));
|
3122
|
+
output.write(col);
|
3123
|
+
|
3124
|
+
} else {
|
3125
|
+
rle = 1;
|
3126
|
+
while (index + rle < maxLen) {
|
3127
|
+
int cscan = pixels[index + rle] & 0xff;
|
3128
|
+
if ((col != cscan && rle < 128) || rle < 3) {
|
3129
|
+
currChunk[rle] = col = cscan;
|
3130
|
+
} else {
|
3131
|
+
if (col == cscan) rle -= 2;
|
3132
|
+
break;
|
3133
|
+
}
|
3134
|
+
rle++;
|
3135
|
+
}
|
3136
|
+
output.write(rle - 1);
|
3137
|
+
for (int i = 0; i < rle; i++) output.write(currChunk[i]);
|
3138
|
+
}
|
3139
|
+
index += rle;
|
3140
|
+
}
|
3141
|
+
} else { // export 24/32 bit TARGA
|
3142
|
+
while (index < maxLen) {
|
3143
|
+
boolean isRLE = false;
|
3144
|
+
currChunk[0] = col = pixels[index];
|
3145
|
+
int rle = 1;
|
3146
|
+
// try to find repeating bytes (min. len = 2 pixels)
|
3147
|
+
// maximum chunk size is 128 pixels
|
3148
|
+
while (index + rle < maxLen) {
|
3149
|
+
if (col != pixels[index + rle] || rle == 128) {
|
3150
|
+
isRLE = (rle > 1); // set flag for RLE chunk
|
3151
|
+
break;
|
3152
|
+
}
|
3153
|
+
rle++;
|
3154
|
+
}
|
3155
|
+
if (isRLE) {
|
3156
|
+
output.write(128 | (rle - 1));
|
3157
|
+
output.write(col & 0xff);
|
3158
|
+
output.write(col >> 8 & 0xff);
|
3159
|
+
output.write(col >> 16 & 0xff);
|
3160
|
+
if (format == ARGB) output.write(col >>> 24 & 0xff);
|
3161
|
+
|
3162
|
+
} else { // not RLE
|
3163
|
+
rle = 1;
|
3164
|
+
while (index + rle < maxLen) {
|
3165
|
+
if ((col != pixels[index + rle] && rle < 128) || rle < 3) {
|
3166
|
+
currChunk[rle] = col = pixels[index + rle];
|
3167
|
+
} else {
|
3168
|
+
// check if the exit condition was the start of
|
3169
|
+
// a repeating colour
|
3170
|
+
if (col == pixels[index + rle]) rle -= 2;
|
3171
|
+
break;
|
3172
|
+
}
|
3173
|
+
rle++;
|
3174
|
+
}
|
3175
|
+
// write uncompressed chunk
|
3176
|
+
output.write(rle - 1);
|
3177
|
+
if (format == ARGB) {
|
3178
|
+
for (int i = 0; i < rle; i++) {
|
3179
|
+
col = currChunk[i];
|
3180
|
+
output.write(col & 0xff);
|
3181
|
+
output.write(col >> 8 & 0xff);
|
3182
|
+
output.write(col >> 16 & 0xff);
|
3183
|
+
output.write(col >>> 24 & 0xff);
|
3184
|
+
}
|
3185
|
+
} else {
|
3186
|
+
for (int i = 0; i < rle; i++) {
|
3187
|
+
col = currChunk[i];
|
3188
|
+
output.write(col & 0xff);
|
3189
|
+
output.write(col >> 8 & 0xff);
|
3190
|
+
output.write(col >> 16 & 0xff);
|
3191
|
+
}
|
3192
|
+
}
|
3193
|
+
}
|
3194
|
+
index += rle;
|
3195
|
+
}
|
3196
|
+
}
|
3197
|
+
output.flush();
|
3198
|
+
return true;
|
3199
|
+
|
3200
|
+
} catch (IOException e) {
|
3201
|
+
e.printStackTrace();
|
3202
|
+
return false;
|
3203
|
+
}
|
3204
|
+
}
|
3205
|
+
|
3206
|
+
|
3207
|
+
/**
|
3208
|
+
* Use ImageIO functions from Java 1.4 and later to handle image save.
|
3209
|
+
* Various formats are supported, typically jpeg, png, bmp, and wbmp.
|
3210
|
+
* To get a list of the supported formats for writing, use: <BR>
|
3211
|
+
* <TT>println(javax.imageio.ImageIO.getReaderFormatNames())</TT>
|
3212
|
+
*/
|
3213
|
+
protected boolean saveImageIO(String path) throws IOException {
|
3214
|
+
try {
|
3215
|
+
int outputFormat = (format == ARGB) ?
|
3216
|
+
BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB;
|
3217
|
+
|
3218
|
+
String extension =
|
3219
|
+
path.substring(path.lastIndexOf('.') + 1).toLowerCase();
|
3220
|
+
|
3221
|
+
// JPEG and BMP images that have an alpha channel set get pretty unhappy.
|
3222
|
+
// BMP just doesn't write, and JPEG writes it as a CMYK image.
|
3223
|
+
// http://code.google.com/p/processing/issues/detail?id=415
|
3224
|
+
if (extension.equals("bmp") || extension.equals("jpg") || extension.equals("jpeg")) {
|
3225
|
+
outputFormat = BufferedImage.TYPE_INT_RGB;
|
3226
|
+
}
|
3227
|
+
|
3228
|
+
BufferedImage bimage = new BufferedImage(pixelWidth, pixelHeight, outputFormat);
|
3229
|
+
bimage.setRGB(0, 0, pixelWidth, pixelHeight, pixels, 0, pixelWidth);
|
3230
|
+
|
3231
|
+
File file = new File(path);
|
3232
|
+
|
3233
|
+
ImageWriter writer = null;
|
3234
|
+
ImageWriteParam param = null;
|
3235
|
+
IIOMetadata metadata = null;
|
3236
|
+
|
3237
|
+
if (extension.equals("jpg") || extension.equals("jpeg")) {
|
3238
|
+
if ((writer = imageioWriter("jpeg")) != null) {
|
3239
|
+
// Set JPEG quality to 90% with baseline optimization. Setting this
|
3240
|
+
// to 1 was a huge jump (about triple the size), so this seems good.
|
3241
|
+
// Oddly, a smaller file size than Photoshop at 90%, but I suppose
|
3242
|
+
// it's a completely different algorithm.
|
3243
|
+
param = writer.getDefaultWriteParam();
|
3244
|
+
param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
|
3245
|
+
param.setCompressionQuality(0.9f);
|
3246
|
+
}
|
3247
|
+
}
|
3248
|
+
|
3249
|
+
if (extension.equals("png")) {
|
3250
|
+
if ((writer = imageioWriter("png")) != null) {
|
3251
|
+
param = writer.getDefaultWriteParam();
|
3252
|
+
if (false) {
|
3253
|
+
metadata = imageioDPI(writer, param, 100);
|
3254
|
+
}
|
3255
|
+
}
|
3256
|
+
}
|
3257
|
+
|
3258
|
+
if (writer != null) {
|
3259
|
+
BufferedOutputStream output =
|
3260
|
+
new BufferedOutputStream(PApplet.createOutput(file));
|
3261
|
+
writer.setOutput(ImageIO.createImageOutputStream(output));
|
3262
|
+
// writer.write(null, new IIOImage(bimage, null, null), param);
|
3263
|
+
writer.write(metadata, new IIOImage(bimage, null, metadata), param);
|
3264
|
+
writer.dispose();
|
3265
|
+
|
3266
|
+
output.flush();
|
3267
|
+
output.close();
|
3268
|
+
return true;
|
3269
|
+
}
|
3270
|
+
// If iter.hasNext() somehow fails up top, it falls through to here
|
3271
|
+
return javax.imageio.ImageIO.write(bimage, extension, file);
|
3272
|
+
|
3273
|
+
} catch (Exception e) {
|
3274
|
+
e.printStackTrace();
|
3275
|
+
throw new IOException("image save failed.");
|
3276
|
+
}
|
3277
|
+
}
|
3278
|
+
|
3279
|
+
|
3280
|
+
private ImageWriter imageioWriter(String extension) {
|
3281
|
+
Iterator<ImageWriter> iter = ImageIO.getImageWritersByFormatName(extension);
|
3282
|
+
if (iter.hasNext()) {
|
3283
|
+
return iter.next();
|
3284
|
+
}
|
3285
|
+
return null;
|
3286
|
+
}
|
3287
|
+
|
3288
|
+
|
3289
|
+
private IIOMetadata imageioDPI(ImageWriter writer, ImageWriteParam param, double dpi) {
|
3290
|
+
// http://stackoverflow.com/questions/321736/how-to-set-dpi-information-in-an-image
|
3291
|
+
ImageTypeSpecifier typeSpecifier =
|
3292
|
+
ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_RGB);
|
3293
|
+
IIOMetadata metadata =
|
3294
|
+
writer.getDefaultImageMetadata(typeSpecifier, param);
|
3295
|
+
|
3296
|
+
if (!metadata.isReadOnly() && metadata.isStandardMetadataFormatSupported()) {
|
3297
|
+
// for PNG, it's dots per millimeter
|
3298
|
+
double dotsPerMilli = dpi / 25.4;
|
3299
|
+
|
3300
|
+
IIOMetadataNode horiz = new IIOMetadataNode("HorizontalPixelSize");
|
3301
|
+
horiz.setAttribute("value", Double.toString(dotsPerMilli));
|
3302
|
+
|
3303
|
+
IIOMetadataNode vert = new IIOMetadataNode("VerticalPixelSize");
|
3304
|
+
vert.setAttribute("value", Double.toString(dotsPerMilli));
|
3305
|
+
|
3306
|
+
IIOMetadataNode dim = new IIOMetadataNode("Dimension");
|
3307
|
+
dim.appendChild(horiz);
|
3308
|
+
dim.appendChild(vert);
|
3309
|
+
|
3310
|
+
IIOMetadataNode root = new IIOMetadataNode("javax_imageio_1.0");
|
3311
|
+
root.appendChild(dim);
|
3312
|
+
|
3313
|
+
try {
|
3314
|
+
metadata.mergeTree("javax_imageio_1.0", root);
|
3315
|
+
return metadata;
|
3316
|
+
|
3317
|
+
} catch (IIOInvalidTreeException e) {
|
3318
|
+
System.err.println("Could not set the DPI of the output image");
|
3319
|
+
e.printStackTrace();
|
3320
|
+
}
|
3321
|
+
}
|
3322
|
+
return null;
|
3323
|
+
}
|
3324
|
+
|
3325
|
+
|
3326
|
+
protected String[] saveImageFormats;
|
3327
|
+
|
3328
|
+
/**
|
3329
|
+
* ( begin auto-generated from PImage_save.xml )
|
3330
|
+
*
|
3331
|
+
* Saves the image into a file. Append a file extension to the name of
|
3332
|
+
* the file, to indicate the file format to be used: either TIFF (.tif),
|
3333
|
+
* TARGA (.tga), JPEG (.jpg), or PNG (.png). If no extension is included
|
3334
|
+
* in the filename, the image will save in TIFF format and .tif will be
|
3335
|
+
* added to the name. These files are saved to the sketch's folder, which
|
3336
|
+
* may be opened by selecting "Show sketch folder" from the "Sketch" menu.
|
3337
|
+
* <br /><br />To save an image created within the code, rather
|
3338
|
+
* than through loading, it's necessary to make the image with the
|
3339
|
+
* <b>createImage()</b> function so it is aware of the location of the
|
3340
|
+
* program and can therefore save the file to the right place. See the
|
3341
|
+
* <b>createImage()</b> reference for more information.
|
3342
|
+
*
|
3343
|
+
* ( end auto-generated )
|
3344
|
+
* <h3>Advanced</h3>
|
3345
|
+
* Save this image to disk.
|
3346
|
+
* <p>
|
3347
|
+
* As of revision 0100, this function requires an absolute path,
|
3348
|
+
* in order to avoid confusion. To save inside the sketch folder,
|
3349
|
+
* use the function savePath() from PApplet, or use saveFrame() instead.
|
3350
|
+
* As of revision 0116, savePath() is not needed if this object has been
|
3351
|
+
* created (as recommended) via createImage() or createGraphics() or
|
3352
|
+
* one of its neighbors.
|
3353
|
+
* <p>
|
3354
|
+
* As of revision 0115, when using Java 1.4 and later, you can write
|
3355
|
+
* to several formats besides tga and tiff. If Java 1.4 is installed
|
3356
|
+
* and the extension used is supported (usually png, jpg, jpeg, bmp,
|
3357
|
+
* and tiff), then those methods will be used to write the image.
|
3358
|
+
* To get a list of the supported formats for writing, use: <BR>
|
3359
|
+
* <TT>println(javax.imageio.ImageIO.getReaderFormatNames())</TT>
|
3360
|
+
* <p>
|
3361
|
+
* To use the original built-in image writers, use .tga or .tif as the
|
3362
|
+
* extension, or don't include an extension. When no extension is used,
|
3363
|
+
* the extension .tif will be added to the file name.
|
3364
|
+
* <p>
|
3365
|
+
* The ImageIO API claims to support wbmp files, however they probably
|
3366
|
+
* require a black and white image. Basic testing produced a zero-length
|
3367
|
+
* file with no error.
|
3368
|
+
*
|
3369
|
+
* @webref pimage:method
|
3370
|
+
* @brief Saves the image to a TIFF, TARGA, PNG, or JPEG file
|
3371
|
+
* @usage application
|
3372
|
+
* @param filename a sequence of letters and numbers
|
3373
|
+
*/
|
3374
|
+
public boolean save(String filename) { // ignore
|
3375
|
+
boolean success = false;
|
3376
|
+
|
3377
|
+
if (parent != null) {
|
3378
|
+
// use savePath(), so that the intermediate directories are created
|
3379
|
+
filename = parent.savePath(filename);
|
3380
|
+
|
3381
|
+
} else {
|
3382
|
+
File file = new File(filename);
|
3383
|
+
if (file.isAbsolute()) {
|
3384
|
+
// make sure that the intermediate folders have been created
|
3385
|
+
PApplet.createPath(file);
|
3386
|
+
} else {
|
3387
|
+
String msg =
|
3388
|
+
"PImage.save() requires an absolute path. " +
|
3389
|
+
"Use createImage(), or pass savePath() to save().";
|
3390
|
+
PGraphics.showException(msg);
|
3391
|
+
}
|
3392
|
+
}
|
3393
|
+
|
3394
|
+
// Make sure the pixel data is ready to go
|
3395
|
+
loadPixels();
|
3396
|
+
|
3397
|
+
try {
|
3398
|
+
OutputStream os = null;
|
3399
|
+
|
3400
|
+
if (saveImageFormats == null) {
|
3401
|
+
saveImageFormats = javax.imageio.ImageIO.getWriterFormatNames();
|
3402
|
+
}
|
3403
|
+
if (saveImageFormats != null) {
|
3404
|
+
for (int i = 0; i < saveImageFormats.length; i++) {
|
3405
|
+
if (filename.endsWith("." + saveImageFormats[i])) {
|
3406
|
+
if (!saveImageIO(filename)) {
|
3407
|
+
System.err.println("Error while saving image.");
|
3408
|
+
return false;
|
3409
|
+
}
|
3410
|
+
return true;
|
3411
|
+
}
|
3412
|
+
}
|
3413
|
+
}
|
3414
|
+
|
3415
|
+
if (filename.toLowerCase().endsWith(".tga")) {
|
3416
|
+
os = new BufferedOutputStream(new FileOutputStream(filename), 32768);
|
3417
|
+
success = saveTGA(os); //, pixels, width, height, format);
|
3418
|
+
|
3419
|
+
} else {
|
3420
|
+
if (!filename.toLowerCase().endsWith(".tif") &&
|
3421
|
+
!filename.toLowerCase().endsWith(".tiff")) {
|
3422
|
+
// if no .tif extension, add it..
|
3423
|
+
filename += ".tif";
|
3424
|
+
}
|
3425
|
+
os = new BufferedOutputStream(new FileOutputStream(filename), 32768);
|
3426
|
+
success = saveTIFF(os); //, pixels, width, height);
|
3427
|
+
}
|
3428
|
+
os.flush();
|
3429
|
+
os.close();
|
3430
|
+
|
3431
|
+
} catch (IOException e) {
|
3432
|
+
System.err.println("Error while saving image.");
|
3433
|
+
e.printStackTrace();
|
3434
|
+
success = false;
|
3435
|
+
}
|
3436
|
+
return success;
|
3437
|
+
}
|
3438
|
+
}
|