2
|
1 |
/*
|
|
2 |
* Copyright 2006 Sun Microsystems, Inc. All Rights Reserved.
|
|
3 |
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
|
4 |
*
|
|
5 |
* This code is free software; you can redistribute it and/or modify it
|
|
6 |
* under the terms of the GNU General Public License version 2 only, as
|
|
7 |
* published by the Free Software Foundation. Sun designates this
|
|
8 |
* particular file as subject to the "Classpath" exception as provided
|
|
9 |
* by Sun in the LICENSE file that accompanied this code.
|
|
10 |
*
|
|
11 |
* This code is distributed in the hope that it will be useful, but WITHOUT
|
|
12 |
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
13 |
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
14 |
* version 2 for more details (a copy is included in the LICENSE file that
|
|
15 |
* accompanied this code).
|
|
16 |
*
|
|
17 |
* You should have received a copy of the GNU General Public License version
|
|
18 |
* 2 along with this work; if not, write to the Free Software Foundation,
|
|
19 |
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
20 |
*
|
|
21 |
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
|
|
22 |
* CA 95054 USA or visit www.sun.com if you need additional information or
|
|
23 |
* have any questions.
|
|
24 |
*/
|
|
25 |
|
|
26 |
package java.awt;
|
|
27 |
|
|
28 |
import java.awt.MultipleGradientPaint.CycleMethod;
|
|
29 |
import java.awt.MultipleGradientPaint.ColorSpaceType;
|
|
30 |
import java.awt.geom.AffineTransform;
|
|
31 |
import java.awt.geom.Rectangle2D;
|
|
32 |
import java.awt.image.ColorModel;
|
|
33 |
|
|
34 |
/**
|
|
35 |
* Provides the actual implementation for the RadialGradientPaint.
|
|
36 |
* This is where the pixel processing is done. A RadialGradienPaint
|
|
37 |
* only supports circular gradients, but it should be possible to scale
|
|
38 |
* the circle to look approximately elliptical, by means of a
|
|
39 |
* gradient transform passed into the RadialGradientPaint constructor.
|
|
40 |
*
|
|
41 |
* @author Nicholas Talian, Vincent Hardy, Jim Graham, Jerry Evans
|
|
42 |
*/
|
|
43 |
final class RadialGradientPaintContext extends MultipleGradientPaintContext {
|
|
44 |
|
|
45 |
/** True when (focus == center). */
|
|
46 |
private boolean isSimpleFocus = false;
|
|
47 |
|
|
48 |
/** True when (cycleMethod == NO_CYCLE). */
|
|
49 |
private boolean isNonCyclic = false;
|
|
50 |
|
|
51 |
/** Radius of the outermost circle defining the 100% gradient stop. */
|
|
52 |
private float radius;
|
|
53 |
|
|
54 |
/** Variables representing center and focus points. */
|
|
55 |
private float centerX, centerY, focusX, focusY;
|
|
56 |
|
|
57 |
/** Radius of the gradient circle squared. */
|
|
58 |
private float radiusSq;
|
|
59 |
|
|
60 |
/** Constant part of X, Y user space coordinates. */
|
|
61 |
private float constA, constB;
|
|
62 |
|
|
63 |
/** Constant second order delta for simple loop. */
|
|
64 |
private float gDeltaDelta;
|
|
65 |
|
|
66 |
/**
|
|
67 |
* This value represents the solution when focusX == X. It is called
|
|
68 |
* trivial because it is easier to calculate than the general case.
|
|
69 |
*/
|
|
70 |
private float trivial;
|
|
71 |
|
|
72 |
/** Amount for offset when clamping focus. */
|
|
73 |
private static final float SCALEBACK = .99f;
|
|
74 |
|
|
75 |
/**
|
|
76 |
* Constructor for RadialGradientPaintContext.
|
|
77 |
*
|
|
78 |
* @param paint the {@code RadialGradientPaint} from which this context
|
|
79 |
* is created
|
|
80 |
* @param cm the {@code ColorModel} that receives
|
|
81 |
* the {@code Paint} data (this is used only as a hint)
|
|
82 |
* @param deviceBounds the device space bounding box of the
|
|
83 |
* graphics primitive being rendered
|
|
84 |
* @param userBounds the user space bounding box of the
|
|
85 |
* graphics primitive being rendered
|
|
86 |
* @param t the {@code AffineTransform} from user
|
|
87 |
* space into device space (gradientTransform should be
|
|
88 |
* concatenated with this)
|
|
89 |
* @param hints the hints that the context object uses to choose
|
|
90 |
* between rendering alternatives
|
|
91 |
* @param cx the center X coordinate in user space of the circle defining
|
|
92 |
* the gradient. The last color of the gradient is mapped to
|
|
93 |
* the perimeter of this circle.
|
|
94 |
* @param cy the center Y coordinate in user space of the circle defining
|
|
95 |
* the gradient. The last color of the gradient is mapped to
|
|
96 |
* the perimeter of this circle.
|
|
97 |
* @param r the radius of the circle defining the extents of the
|
|
98 |
* color gradient
|
|
99 |
* @param fx the X coordinate in user space to which the first color
|
|
100 |
* is mapped
|
|
101 |
* @param fy the Y coordinate in user space to which the first color
|
|
102 |
* is mapped
|
|
103 |
* @param fractions the fractions specifying the gradient distribution
|
|
104 |
* @param colors the gradient colors
|
|
105 |
* @param cycleMethod either NO_CYCLE, REFLECT, or REPEAT
|
|
106 |
* @param colorSpace which colorspace to use for interpolation,
|
|
107 |
* either SRGB or LINEAR_RGB
|
|
108 |
*/
|
|
109 |
RadialGradientPaintContext(RadialGradientPaint paint,
|
|
110 |
ColorModel cm,
|
|
111 |
Rectangle deviceBounds,
|
|
112 |
Rectangle2D userBounds,
|
|
113 |
AffineTransform t,
|
|
114 |
RenderingHints hints,
|
|
115 |
float cx, float cy,
|
|
116 |
float r,
|
|
117 |
float fx, float fy,
|
|
118 |
float[] fractions,
|
|
119 |
Color[] colors,
|
|
120 |
CycleMethod cycleMethod,
|
|
121 |
ColorSpaceType colorSpace)
|
|
122 |
{
|
|
123 |
super(paint, cm, deviceBounds, userBounds, t, hints,
|
|
124 |
fractions, colors, cycleMethod, colorSpace);
|
|
125 |
|
|
126 |
// copy some parameters
|
|
127 |
centerX = cx;
|
|
128 |
centerY = cy;
|
|
129 |
focusX = fx;
|
|
130 |
focusY = fy;
|
|
131 |
radius = r;
|
|
132 |
|
|
133 |
this.isSimpleFocus = (focusX == centerX) && (focusY == centerY);
|
|
134 |
this.isNonCyclic = (cycleMethod == CycleMethod.NO_CYCLE);
|
|
135 |
|
|
136 |
// for use in the quadractic equation
|
|
137 |
radiusSq = radius * radius;
|
|
138 |
|
|
139 |
float dX = focusX - centerX;
|
|
140 |
float dY = focusY - centerY;
|
|
141 |
|
|
142 |
double distSq = (dX * dX) + (dY * dY);
|
|
143 |
|
|
144 |
// test if distance from focus to center is greater than the radius
|
|
145 |
if (distSq > radiusSq * SCALEBACK) {
|
|
146 |
// clamp focus to radius
|
|
147 |
float scalefactor = (float)Math.sqrt(radiusSq * SCALEBACK / distSq);
|
|
148 |
dX = dX * scalefactor;
|
|
149 |
dY = dY * scalefactor;
|
|
150 |
focusX = centerX + dX;
|
|
151 |
focusY = centerY + dY;
|
|
152 |
}
|
|
153 |
|
|
154 |
// calculate the solution to be used in the case where X == focusX
|
|
155 |
// in cyclicCircularGradientFillRaster()
|
|
156 |
trivial = (float)Math.sqrt(radiusSq - (dX * dX));
|
|
157 |
|
|
158 |
// constant parts of X, Y user space coordinates
|
|
159 |
constA = a02 - centerX;
|
|
160 |
constB = a12 - centerY;
|
|
161 |
|
|
162 |
// constant second order delta for simple loop
|
|
163 |
gDeltaDelta = 2 * ( a00 * a00 + a10 * a10) / radiusSq;
|
|
164 |
}
|
|
165 |
|
|
166 |
/**
|
|
167 |
* Return a Raster containing the colors generated for the graphics
|
|
168 |
* operation.
|
|
169 |
*
|
|
170 |
* @param x,y,w,h the area in device space for which colors are
|
|
171 |
* generated.
|
|
172 |
*/
|
|
173 |
protected void fillRaster(int pixels[], int off, int adjust,
|
|
174 |
int x, int y, int w, int h)
|
|
175 |
{
|
|
176 |
if (isSimpleFocus && isNonCyclic && isSimpleLookup) {
|
|
177 |
simpleNonCyclicFillRaster(pixels, off, adjust, x, y, w, h);
|
|
178 |
} else {
|
|
179 |
cyclicCircularGradientFillRaster(pixels, off, adjust, x, y, w, h);
|
|
180 |
}
|
|
181 |
}
|
|
182 |
|
|
183 |
/**
|
|
184 |
* This code works in the simplest of cases, where the focus == center
|
|
185 |
* point, the gradient is noncyclic, and the gradient lookup method is
|
|
186 |
* fast (single array index, no conversion necessary).
|
|
187 |
*/
|
|
188 |
private void simpleNonCyclicFillRaster(int pixels[], int off, int adjust,
|
|
189 |
int x, int y, int w, int h)
|
|
190 |
{
|
|
191 |
/* We calculate sqrt(X^2 + Y^2) relative to the radius
|
|
192 |
* size to get the fraction for the color to use.
|
|
193 |
*
|
|
194 |
* Each step along the scanline adds (a00, a10) to (X, Y).
|
|
195 |
* If we precalculate:
|
|
196 |
* gRel = X^2+Y^2
|
|
197 |
* for the start of the row, then for each step we need to
|
|
198 |
* calculate:
|
|
199 |
* gRel' = (X+a00)^2 + (Y+a10)^2
|
|
200 |
* = X^2 + 2*X*a00 + a00^2 + Y^2 + 2*Y*a10 + a10^2
|
|
201 |
* = (X^2+Y^2) + 2*(X*a00+Y*a10) + (a00^2+a10^2)
|
|
202 |
* = gRel + 2*(X*a00+Y*a10) + (a00^2+a10^2)
|
|
203 |
* = gRel + 2*DP + SD
|
|
204 |
* (where DP = dot product between X,Y and a00,a10
|
|
205 |
* and SD = dot product square of the delta vector)
|
|
206 |
* For the step after that we get:
|
|
207 |
* gRel'' = (X+2*a00)^2 + (Y+2*a10)^2
|
|
208 |
* = X^2 + 4*X*a00 + 4*a00^2 + Y^2 + 4*Y*a10 + 4*a10^2
|
|
209 |
* = (X^2+Y^2) + 4*(X*a00+Y*a10) + 4*(a00^2+a10^2)
|
|
210 |
* = gRel + 4*DP + 4*SD
|
|
211 |
* = gRel' + 2*DP + 3*SD
|
|
212 |
* The increment changed by:
|
|
213 |
* (gRel'' - gRel') - (gRel' - gRel)
|
|
214 |
* = (2*DP + 3*SD) - (2*DP + SD)
|
|
215 |
* = 2*SD
|
|
216 |
* Note that this value depends only on the (inverse of the)
|
|
217 |
* transformation matrix and so is a constant for the loop.
|
|
218 |
* To make this all relative to the unit circle, we need to
|
|
219 |
* divide all values as follows:
|
|
220 |
* [XY] /= radius
|
|
221 |
* gRel /= radiusSq
|
|
222 |
* DP /= radiusSq
|
|
223 |
* SD /= radiusSq
|
|
224 |
*/
|
|
225 |
// coordinates of UL corner in "user space" relative to center
|
|
226 |
float rowX = (a00*x) + (a01*y) + constA;
|
|
227 |
float rowY = (a10*x) + (a11*y) + constB;
|
|
228 |
|
|
229 |
// second order delta calculated in constructor
|
|
230 |
float gDeltaDelta = this.gDeltaDelta;
|
|
231 |
|
|
232 |
// adjust is (scan-w) of pixels array, we need (scan)
|
|
233 |
adjust += w;
|
|
234 |
|
|
235 |
// rgb of the 1.0 color used when the distance exceeds gradient radius
|
|
236 |
int rgbclip = gradient[fastGradientArraySize];
|
|
237 |
|
|
238 |
for (int j = 0; j < h; j++) {
|
|
239 |
// these values depend on the coordinates of the start of the row
|
|
240 |
float gRel = (rowX * rowX + rowY * rowY) / radiusSq;
|
|
241 |
float gDelta = (2 * ( a00 * rowX + a10 * rowY) / radiusSq +
|
|
242 |
gDeltaDelta/2);
|
|
243 |
|
|
244 |
/* Use optimized loops for any cases where gRel >= 1.
|
|
245 |
* We do not need to calculate sqrt(gRel) for these
|
|
246 |
* values since sqrt(N>=1) == (M>=1).
|
|
247 |
* Note that gRel follows a parabola which can only be < 1
|
|
248 |
* for a small region around the center on each scanline. In
|
|
249 |
* particular:
|
|
250 |
* gDeltaDelta is always positive
|
|
251 |
* gDelta is <0 until it crosses the midpoint, then >0
|
|
252 |
* To the left and right of that region, it will always be
|
|
253 |
* >=1 out to infinity, so we can process the line in 3
|
|
254 |
* regions:
|
|
255 |
* out to the left - quick fill until gRel < 1, updating gRel
|
|
256 |
* in the heart - slow fraction=sqrt fill while gRel < 1
|
|
257 |
* out to the right - quick fill rest of scanline, ignore gRel
|
|
258 |
*/
|
|
259 |
int i = 0;
|
|
260 |
// Quick fill for "out to the left"
|
|
261 |
while (i < w && gRel >= 1.0f) {
|
|
262 |
pixels[off + i] = rgbclip;
|
|
263 |
gRel += gDelta;
|
|
264 |
gDelta += gDeltaDelta;
|
|
265 |
i++;
|
|
266 |
}
|
|
267 |
// Slow fill for "in the heart"
|
|
268 |
while (i < w && gRel < 1.0f) {
|
|
269 |
int gIndex;
|
|
270 |
|
|
271 |
if (gRel <= 0) {
|
|
272 |
gIndex = 0;
|
|
273 |
} else {
|
|
274 |
float fIndex = gRel * SQRT_LUT_SIZE;
|
|
275 |
int iIndex = (int) (fIndex);
|
|
276 |
float s0 = sqrtLut[iIndex];
|
|
277 |
float s1 = sqrtLut[iIndex+1] - s0;
|
|
278 |
fIndex = s0 + (fIndex - iIndex) * s1;
|
|
279 |
gIndex = (int) (fIndex * fastGradientArraySize);
|
|
280 |
}
|
|
281 |
|
|
282 |
// store the color at this point
|
|
283 |
pixels[off + i] = gradient[gIndex];
|
|
284 |
|
|
285 |
// incremental calculation
|
|
286 |
gRel += gDelta;
|
|
287 |
gDelta += gDeltaDelta;
|
|
288 |
i++;
|
|
289 |
}
|
|
290 |
// Quick fill to end of line for "out to the right"
|
|
291 |
while (i < w) {
|
|
292 |
pixels[off + i] = rgbclip;
|
|
293 |
i++;
|
|
294 |
}
|
|
295 |
|
|
296 |
off += adjust;
|
|
297 |
rowX += a01;
|
|
298 |
rowY += a11;
|
|
299 |
}
|
|
300 |
}
|
|
301 |
|
|
302 |
// SQRT_LUT_SIZE must be a power of 2 for the test above to work.
|
|
303 |
private static final int SQRT_LUT_SIZE = (1 << 11);
|
|
304 |
private static float sqrtLut[] = new float[SQRT_LUT_SIZE+1];
|
|
305 |
static {
|
|
306 |
for (int i = 0; i < sqrtLut.length; i++) {
|
|
307 |
sqrtLut[i] = (float) Math.sqrt(i / ((float) SQRT_LUT_SIZE));
|
|
308 |
}
|
|
309 |
}
|
|
310 |
|
|
311 |
/**
|
|
312 |
* Fill the raster, cycling the gradient colors when a point falls outside
|
|
313 |
* of the perimeter of the 100% stop circle.
|
|
314 |
*
|
|
315 |
* This calculation first computes the intersection point of the line
|
|
316 |
* from the focus through the current point in the raster, and the
|
|
317 |
* perimeter of the gradient circle.
|
|
318 |
*
|
|
319 |
* Then it determines the percentage distance of the current point along
|
|
320 |
* that line (focus is 0%, perimeter is 100%).
|
|
321 |
*
|
|
322 |
* Equation of a circle centered at (a,b) with radius r:
|
|
323 |
* (x-a)^2 + (y-b)^2 = r^2
|
|
324 |
* Equation of a line with slope m and y-intercept b:
|
|
325 |
* y = mx + b
|
|
326 |
* Replacing y in the circle equation and solving using the quadratic
|
|
327 |
* formula produces the following set of equations. Constant factors have
|
|
328 |
* been extracted out of the inner loop.
|
|
329 |
*/
|
|
330 |
private void cyclicCircularGradientFillRaster(int pixels[], int off,
|
|
331 |
int adjust,
|
|
332 |
int x, int y,
|
|
333 |
int w, int h)
|
|
334 |
{
|
|
335 |
// constant part of the C factor of the quadratic equation
|
|
336 |
final double constC =
|
|
337 |
-radiusSq + (centerX * centerX) + (centerY * centerY);
|
|
338 |
|
|
339 |
// coefficients of the quadratic equation (Ax^2 + Bx + C = 0)
|
|
340 |
double A, B, C;
|
|
341 |
|
|
342 |
// slope and y-intercept of the focus-perimeter line
|
|
343 |
double slope, yintcpt;
|
|
344 |
|
|
345 |
// intersection with circle X,Y coordinate
|
|
346 |
double solutionX, solutionY;
|
|
347 |
|
|
348 |
// constant parts of X, Y coordinates
|
|
349 |
final float constX = (a00*x) + (a01*y) + a02;
|
|
350 |
final float constY = (a10*x) + (a11*y) + a12;
|
|
351 |
|
|
352 |
// constants in inner loop quadratic formula
|
|
353 |
final float precalc2 = 2 * centerY;
|
|
354 |
final float precalc3 = -2 * centerX;
|
|
355 |
|
|
356 |
// value between 0 and 1 specifying position in the gradient
|
|
357 |
float g;
|
|
358 |
|
|
359 |
// determinant of quadratic formula (should always be > 0)
|
|
360 |
float det;
|
|
361 |
|
|
362 |
// sq distance from the current point to focus
|
|
363 |
float currentToFocusSq;
|
|
364 |
|
|
365 |
// sq distance from the intersect point to focus
|
|
366 |
float intersectToFocusSq;
|
|
367 |
|
|
368 |
// temp variables for change in X,Y squared
|
|
369 |
float deltaXSq, deltaYSq;
|
|
370 |
|
|
371 |
// used to index pixels array
|
|
372 |
int indexer = off;
|
|
373 |
|
|
374 |
// incremental index change for pixels array
|
|
375 |
int pixInc = w+adjust;
|
|
376 |
|
|
377 |
// for every row
|
|
378 |
for (int j = 0; j < h; j++) {
|
|
379 |
|
|
380 |
// user space point; these are constant from column to column
|
|
381 |
float X = (a01*j) + constX;
|
|
382 |
float Y = (a11*j) + constY;
|
|
383 |
|
|
384 |
// for every column (inner loop begins here)
|
|
385 |
for (int i = 0; i < w; i++) {
|
|
386 |
|
|
387 |
if (X == focusX) {
|
|
388 |
// special case to avoid divide by zero
|
|
389 |
solutionX = focusX;
|
|
390 |
solutionY = centerY;
|
|
391 |
solutionY += (Y > focusY) ? trivial : -trivial;
|
|
392 |
} else {
|
|
393 |
// slope and y-intercept of the focus-perimeter line
|
|
394 |
slope = (Y - focusY) / (X - focusX);
|
|
395 |
yintcpt = Y - (slope * X);
|
|
396 |
|
|
397 |
// use the quadratic formula to calculate the
|
|
398 |
// intersection point
|
|
399 |
A = (slope * slope) + 1;
|
|
400 |
B = precalc3 + (-2 * slope * (centerY - yintcpt));
|
|
401 |
C = constC + (yintcpt* (yintcpt - precalc2));
|
|
402 |
|
|
403 |
det = (float)Math.sqrt((B * B) - (4 * A * C));
|
|
404 |
solutionX = -B;
|
|
405 |
|
|
406 |
// choose the positive or negative root depending
|
|
407 |
// on where the X coord lies with respect to the focus
|
|
408 |
solutionX += (X < focusX)? -det : det;
|
|
409 |
solutionX = solutionX / (2 * A); // divisor
|
|
410 |
solutionY = (slope * solutionX) + yintcpt;
|
|
411 |
}
|
|
412 |
|
|
413 |
// Calculate the square of the distance from the current point
|
|
414 |
// to the focus and the square of the distance from the
|
|
415 |
// intersection point to the focus. Want the squares so we can
|
|
416 |
// do 1 square root after division instead of 2 before.
|
|
417 |
|
|
418 |
deltaXSq = X - focusX;
|
|
419 |
deltaXSq = deltaXSq * deltaXSq;
|
|
420 |
|
|
421 |
deltaYSq = Y - focusY;
|
|
422 |
deltaYSq = deltaYSq * deltaYSq;
|
|
423 |
|
|
424 |
currentToFocusSq = deltaXSq + deltaYSq;
|
|
425 |
|
|
426 |
deltaXSq = (float)solutionX - focusX;
|
|
427 |
deltaXSq = deltaXSq * deltaXSq;
|
|
428 |
|
|
429 |
deltaYSq = (float)solutionY - focusY;
|
|
430 |
deltaYSq = deltaYSq * deltaYSq;
|
|
431 |
|
|
432 |
intersectToFocusSq = deltaXSq + deltaYSq;
|
|
433 |
|
|
434 |
// get the percentage (0-1) of the current point along the
|
|
435 |
// focus-circumference line
|
|
436 |
g = (float)Math.sqrt(currentToFocusSq / intersectToFocusSq);
|
|
437 |
|
|
438 |
// store the color at this point
|
|
439 |
pixels[indexer + i] = indexIntoGradientsArrays(g);
|
|
440 |
|
|
441 |
// incremental change in X, Y
|
|
442 |
X += a00;
|
|
443 |
Y += a10;
|
|
444 |
} //end inner loop
|
|
445 |
|
|
446 |
indexer += pixInc;
|
|
447 |
} //end outer loop
|
|
448 |
}
|
|
449 |
}
|