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 
  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 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  */
  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 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 }