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 }