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