1/*
2 * Copyright (c) 2006, Oracle and/or its affiliates. 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.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package java.awt;
27
28import java.awt.MultipleGradientPaint.CycleMethod;
29import java.awt.MultipleGradientPaint.ColorSpaceType;
30import java.awt.geom.AffineTransform;
31import java.awt.geom.Rectangle2D;
32import java.awt.image.ColorModel;
33
34/**
35 * Provides the actual implementation for the RadialGradientPaint.
36 * This is where the pixel processing is done.  A RadialGradientPaint
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 */
43final 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 quadratic 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}
450