/*
 * Copyright (c) 2006, 2013, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package com.sun.prism.impl.paint;

import com.sun.javafx.geom.transform.BaseTransform;
import com.sun.prism.paint.Color;
import com.sun.prism.paint.Gradient;
import com.sun.prism.paint.RadialGradient;

Provides the actual implementation for the RadialGradientPaint. This is where the pixel processing is done. A RadialGradienPaint only supports circular gradients, but it should be possible to scale the circle to look approximately elliptical, by means of a gradient transform passed into the RadialGradientPaint constructor.
/** * Provides the actual implementation for the RadialGradientPaint. * This is where the pixel processing is done. A RadialGradienPaint * only supports circular gradients, but it should be possible to scale * the circle to look approximately elliptical, by means of a * gradient transform passed into the RadialGradientPaint constructor. */
final class RadialGradientContext extends MultipleGradientContext {
True when (focus == center).
/** True when (focus == center). */
private boolean isSimpleFocus = false;
True when (cycleMethod == NO_CYCLE).
/** True when (cycleMethod == NO_CYCLE). */
private boolean isNonCyclic = false;
Radius of the outermost circle defining the 100% gradient stop.
/** Radius of the outermost circle defining the 100% gradient stop. */
private float radius;
Variables representing center and focus points.
/** Variables representing center and focus points. */
private float centerX, centerY, focusX, focusY;
Radius of the gradient circle squared.
/** Radius of the gradient circle squared. */
private float radiusSq;
Constant part of X, Y user space coordinates.
/** Constant part of X, Y user space coordinates. */
private float constA, constB;
Constant second order delta for simple loop.
/** Constant second order delta for simple loop. */
private float gDeltaDelta;
This value represents the solution when focusX == X. It is called trivial because it is easier to calculate than the general case.
/** * This value represents the solution when focusX == X. It is called * trivial because it is easier to calculate than the general case. */
private float trivial;
Amount for offset when clamping focus.
/** Amount for offset when clamping focus. */
private static final float SCALEBACK = .99f;
Constructor for RadialGradientContext.
Params:
  • paint – the RadialGradientPaint from which this context is created
  • t – the AffineTransform from user space into device space (gradientTransform should be concatenated with this)
  • cx – the center X coordinate in user space of the circle defining the gradient. The last color of the gradient is mapped to the perimeter of this circle.
  • cy – the center Y coordinate in user space of the circle defining the gradient. The last color of the gradient is mapped to the perimeter of this circle.
  • r – the radius of the circle defining the extents of the color gradient
  • fx – the X coordinate in user space to which the first color is mapped
  • fy – the Y coordinate in user space to which the first color is mapped
  • fractions – the fractions specifying the gradient distribution
  • colors – the gradient colors
  • cycleMethod – either NO_CYCLE, REFLECT, or REPEAT
/** * Constructor for RadialGradientContext. * * @param paint the {@code RadialGradientPaint} from which this context * is created * @param t the {@code AffineTransform} from user * space into device space (gradientTransform should be * concatenated with this) * @param cx the center X coordinate in user space of the circle defining * the gradient. The last color of the gradient is mapped to * the perimeter of this circle. * @param cy the center Y coordinate in user space of the circle defining * the gradient. The last color of the gradient is mapped to * the perimeter of this circle. * @param r the radius of the circle defining the extents of the * color gradient * @param fx the X coordinate in user space to which the first color * is mapped * @param fy the Y coordinate in user space to which the first color * is mapped * @param fractions the fractions specifying the gradient distribution * @param colors the gradient colors * @param cycleMethod either NO_CYCLE, REFLECT, or REPEAT */
RadialGradientContext(RadialGradient paint, BaseTransform t, float cx, float cy, float r, float fx, float fy, float[] fractions, Color[] colors, int cycleMethod) { super(paint, t, fractions, colors, cycleMethod); // copy some parameters centerX = cx; centerY = cy; focusX = fx; focusY = fy; radius = r; this.isSimpleFocus = (focusX == centerX) && (focusY == centerY); this.isNonCyclic = (cycleMethod == Gradient.PAD); // for use in the quadractic equation radiusSq = radius * radius; float dX = focusX - centerX; float dY = focusY - centerY; double distSq = (dX * dX) + (dY * dY); // test if distance from focus to center is greater than the radius if (distSq > radiusSq * SCALEBACK) { // clamp focus to radius float scalefactor = (float)Math.sqrt(radiusSq * SCALEBACK / distSq); dX = dX * scalefactor; dY = dY * scalefactor; focusX = centerX + dX; focusY = centerY + dY; } // calculate the solution to be used in the case where X == focusX // in cyclicCircularGradientFillRaster() trivial = (float)Math.sqrt(radiusSq - (dX * dX)); // constant parts of X, Y user space coordinates constA = a02 - centerX; constB = a12 - centerY; // constant second order delta for simple loop gDeltaDelta = 2 * ( a00 * a00 + a10 * a10) / radiusSq; }
Return a Raster containing the colors generated for the graphics operation.
@paramx,y,w,h the area in device space for which colors are generated.
/** * Return a Raster containing the colors generated for the graphics * operation. * * @param x,y,w,h the area in device space for which colors are * generated. */
protected void fillRaster(int pixels[], int off, int adjust, int x, int y, int w, int h) { if (isSimpleFocus && isNonCyclic && isSimpleLookup) { simpleNonCyclicFillRaster(pixels, off, adjust, x, y, w, h); } else { cyclicCircularGradientFillRaster(pixels, off, adjust, x, y, w, h); } }
This code works in the simplest of cases, where the focus == center point, the gradient is noncyclic, and the gradient lookup method is fast (single array index, no conversion necessary).
/** * This code works in the simplest of cases, where the focus == center * point, the gradient is noncyclic, and the gradient lookup method is * fast (single array index, no conversion necessary). */
private void simpleNonCyclicFillRaster(int pixels[], int off, int adjust, int x, int y, int w, int h) { /* We calculate sqrt(X^2 + Y^2) relative to the radius * size to get the fraction for the color to use. * * Each step along the scanline adds (a00, a10) to (X, Y). * If we precalculate: * gRel = X^2+Y^2 * for the start of the row, then for each step we need to * calculate: * gRel' = (X+a00)^2 + (Y+a10)^2 * = X^2 + 2*X*a00 + a00^2 + Y^2 + 2*Y*a10 + a10^2 * = (X^2+Y^2) + 2*(X*a00+Y*a10) + (a00^2+a10^2) * = gRel + 2*(X*a00+Y*a10) + (a00^2+a10^2) * = gRel + 2*DP + SD * (where DP = dot product between X,Y and a00,a10 * and SD = dot product square of the delta vector) * For the step after that we get: * gRel'' = (X+2*a00)^2 + (Y+2*a10)^2 * = X^2 + 4*X*a00 + 4*a00^2 + Y^2 + 4*Y*a10 + 4*a10^2 * = (X^2+Y^2) + 4*(X*a00+Y*a10) + 4*(a00^2+a10^2) * = gRel + 4*DP + 4*SD * = gRel' + 2*DP + 3*SD * The increment changed by: * (gRel'' - gRel') - (gRel' - gRel) * = (2*DP + 3*SD) - (2*DP + SD) * = 2*SD * Note that this value depends only on the (inverse of the) * transformation matrix and so is a constant for the loop. * To make this all relative to the unit circle, we need to * divide all values as follows: * [XY] /= radius * gRel /= radiusSq * DP /= radiusSq * SD /= radiusSq */ // coordinates of UL corner in "user space" relative to center float rowX = (a00*x) + (a01*y) + constA; float rowY = (a10*x) + (a11*y) + constB; // second order delta calculated in constructor float gDeltaDelta = this.gDeltaDelta; // adjust is (scan-w) of pixels array, we need (scan) adjust += w; // rgb of the 1.0 color used when the distance exceeds gradient radius int rgbclip = gradient[fastGradientArraySize]; for (int j = 0; j < h; j++) { // these values depend on the coordinates of the start of the row float gRel = (rowX * rowX + rowY * rowY) / radiusSq; float gDelta = (2 * ( a00 * rowX + a10 * rowY) / radiusSq + gDeltaDelta/2); /* Use optimized loops for any cases where gRel >= 1. * We do not need to calculate sqrt(gRel) for these * values since sqrt(N>=1) == (M>=1). * Note that gRel follows a parabola which can only be < 1 * for a small region around the center on each scanline. In * particular: * gDeltaDelta is always positive * gDelta is <0 until it crosses the midpoint, then >0 * To the left and right of that region, it will always be * >=1 out to infinity, so we can process the line in 3 * regions: * out to the left - quick fill until gRel < 1, updating gRel * in the heart - slow fraction=sqrt fill while gRel < 1 * out to the right - quick fill rest of scanline, ignore gRel */ int i = 0; // Quick fill for "out to the left" while (i < w && gRel >= 1.0f) { pixels[off + i] = rgbclip; gRel += gDelta; gDelta += gDeltaDelta; i++; } // Slow fill for "in the heart" while (i < w && gRel < 1.0f) { int gIndex; if (gRel <= 0) { gIndex = 0; } else { float fIndex = gRel * SQRT_LUT_SIZE; int iIndex = (int) (fIndex); float s0 = sqrtLut[iIndex]; float s1 = sqrtLut[iIndex+1] - s0; fIndex = s0 + (fIndex - iIndex) * s1; gIndex = (int) (fIndex * fastGradientArraySize); } // store the color at this point pixels[off + i] = gradient[gIndex]; // incremental calculation gRel += gDelta; gDelta += gDeltaDelta; i++; } // Quick fill to end of line for "out to the right" while (i < w) { pixels[off + i] = rgbclip; i++; } off += adjust; rowX += a01; rowY += a11; } } // SQRT_LUT_SIZE must be a power of 2 for the test above to work. private static final int SQRT_LUT_SIZE = (1 << 11); private static float sqrtLut[] = new float[SQRT_LUT_SIZE+1]; static { for (int i = 0; i < sqrtLut.length; i++) { sqrtLut[i] = (float) Math.sqrt(i / ((float) SQRT_LUT_SIZE)); } }
Fill the raster, cycling the gradient colors when a point falls outside of the perimeter of the 100% stop circle. This calculation first computes the intersection point of the line from the focus through the current point in the raster, and the perimeter of the gradient circle. Then it determines the percentage distance of the current point along that line (focus is 0%, perimeter is 100%). Equation of a circle centered at (a,b) with radius r: (x-a)^2 + (y-b)^2 = r^2 Equation of a line with slope m and y-intercept b: y = mx + b Replacing y in the circle equation and solving using the quadratic formula produces the following set of equations. Constant factors have been extracted out of the inner loop.
/** * Fill the raster, cycling the gradient colors when a point falls outside * of the perimeter of the 100% stop circle. * * This calculation first computes the intersection point of the line * from the focus through the current point in the raster, and the * perimeter of the gradient circle. * * Then it determines the percentage distance of the current point along * that line (focus is 0%, perimeter is 100%). * * Equation of a circle centered at (a,b) with radius r: * (x-a)^2 + (y-b)^2 = r^2 * Equation of a line with slope m and y-intercept b: * y = mx + b * Replacing y in the circle equation and solving using the quadratic * formula produces the following set of equations. Constant factors have * been extracted out of the inner loop. */
private void cyclicCircularGradientFillRaster(int pixels[], int off, int adjust, int x, int y, int w, int h) { // constant part of the C factor of the quadratic equation final double constC = -radiusSq + (centerX * centerX) + (centerY * centerY); // coefficients of the quadratic equation (Ax^2 + Bx + C = 0) double A, B, C; // slope and y-intercept of the focus-perimeter line double slope, yintcpt; // intersection with circle X,Y coordinate double solutionX, solutionY; // constant parts of X, Y coordinates final float constX = (a00*x) + (a01*y) + a02; final float constY = (a10*x) + (a11*y) + a12; // constants in inner loop quadratic formula final float precalc2 = 2 * centerY; final float precalc3 = -2 * centerX; // value between 0 and 1 specifying position in the gradient float g; // determinant of quadratic formula (should always be > 0) float det; // sq distance from the current point to focus float currentToFocusSq; // sq distance from the intersect point to focus float intersectToFocusSq; // temp variables for change in X,Y squared float deltaXSq, deltaYSq; // used to index pixels array int indexer = off; // incremental index change for pixels array int pixInc = w+adjust; // for every row for (int j = 0; j < h; j++) { // user space point; these are constant from column to column float X = (a01*j) + constX; float Y = (a11*j) + constY; // for every column (inner loop begins here) for (int i = 0; i < w; i++) { if (X == focusX) { // special case to avoid divide by zero solutionX = focusX; solutionY = centerY; solutionY += (Y > focusY) ? trivial : -trivial; } else { // slope and y-intercept of the focus-perimeter line slope = (Y - focusY) / (X - focusX); yintcpt = Y - (slope * X); // use the quadratic formula to calculate the // intersection point A = (slope * slope) + 1; B = precalc3 + (-2 * slope * (centerY - yintcpt)); C = constC + (yintcpt* (yintcpt - precalc2)); det = (float)Math.sqrt((B * B) - (4 * A * C)); solutionX = -B; // choose the positive or negative root depending // on where the X coord lies with respect to the focus solutionX += (X < focusX)? -det : det; solutionX = solutionX / (2 * A); // divisor solutionY = (slope * solutionX) + yintcpt; } // Calculate the square of the distance from the current point // to the focus and the square of the distance from the // intersection point to the focus. Want the squares so we can // do 1 square root after division instead of 2 before. deltaXSq = X - focusX; deltaXSq = deltaXSq * deltaXSq; deltaYSq = Y - focusY; deltaYSq = deltaYSq * deltaYSq; currentToFocusSq = deltaXSq + deltaYSq; deltaXSq = (float)solutionX - focusX; deltaXSq = deltaXSq * deltaXSq; deltaYSq = (float)solutionY - focusY; deltaYSq = deltaYSq * deltaYSq; intersectToFocusSq = deltaXSq + deltaYSq; // get the percentage (0-1) of the current point along the // focus-circumference line g = (float)Math.sqrt(currentToFocusSq / intersectToFocusSq); // store the color at this point pixels[indexer + i] = indexIntoGradientsArrays(g); // incremental change in X, Y X += a00; Y += a10; } //end inner loop indexer += pixInc; } //end outer loop } }