0N/A/*
2362N/A * Copyright (c) 2006, Oracle and/or its affiliates. All rights reserved.
0N/A * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0N/A *
0N/A * This code is free software; you can redistribute it and/or modify it
0N/A * under the terms of the GNU General Public License version 2 only, as
2362N/A * published by the Free Software Foundation. Oracle designates this
0N/A * particular file as subject to the "Classpath" exception as provided
2362N/A * by Oracle in the LICENSE file that accompanied this code.
0N/A *
0N/A * This code is distributed in the hope that it will be useful, but WITHOUT
0N/A * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0N/A * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0N/A * version 2 for more details (a copy is included in the LICENSE file that
0N/A * accompanied this code).
0N/A *
0N/A * You should have received a copy of the GNU General Public License version
0N/A * 2 along with this work; if not, write to the Free Software Foundation,
0N/A * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0N/A *
2362N/A * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
2362N/A * or visit www.oracle.com if you need additional information or have any
2362N/A * questions.
0N/A */
0N/A
0N/Apackage java.awt;
0N/A
0N/Aimport java.awt.MultipleGradientPaint.CycleMethod;
0N/Aimport java.awt.MultipleGradientPaint.ColorSpaceType;
0N/Aimport java.awt.geom.AffineTransform;
0N/Aimport java.awt.geom.Rectangle2D;
0N/Aimport java.awt.image.ColorModel;
0N/A
0N/A/**
0N/A * Provides the actual implementation for the RadialGradientPaint.
0N/A * This is where the pixel processing is done. A RadialGradienPaint
0N/A * only supports circular gradients, but it should be possible to scale
0N/A * the circle to look approximately elliptical, by means of a
0N/A * gradient transform passed into the RadialGradientPaint constructor.
0N/A *
0N/A * @author Nicholas Talian, Vincent Hardy, Jim Graham, Jerry Evans
0N/A */
0N/Afinal class RadialGradientPaintContext extends MultipleGradientPaintContext {
0N/A
0N/A /** True when (focus == center). */
0N/A private boolean isSimpleFocus = false;
0N/A
0N/A /** True when (cycleMethod == NO_CYCLE). */
0N/A private boolean isNonCyclic = false;
0N/A
0N/A /** Radius of the outermost circle defining the 100% gradient stop. */
0N/A private float radius;
0N/A
0N/A /** Variables representing center and focus points. */
0N/A private float centerX, centerY, focusX, focusY;
0N/A
0N/A /** Radius of the gradient circle squared. */
0N/A private float radiusSq;
0N/A
0N/A /** Constant part of X, Y user space coordinates. */
0N/A private float constA, constB;
0N/A
0N/A /** Constant second order delta for simple loop. */
0N/A private float gDeltaDelta;
0N/A
0N/A /**
0N/A * This value represents the solution when focusX == X. It is called
0N/A * trivial because it is easier to calculate than the general case.
0N/A */
0N/A private float trivial;
0N/A
0N/A /** Amount for offset when clamping focus. */
0N/A private static final float SCALEBACK = .99f;
0N/A
0N/A /**
0N/A * Constructor for RadialGradientPaintContext.
0N/A *
0N/A * @param paint the {@code RadialGradientPaint} from which this context
0N/A * is created
0N/A * @param cm the {@code ColorModel} that receives
0N/A * the {@code Paint} data (this is used only as a hint)
0N/A * @param deviceBounds the device space bounding box of the
0N/A * graphics primitive being rendered
0N/A * @param userBounds the user space bounding box of the
0N/A * graphics primitive being rendered
0N/A * @param t the {@code AffineTransform} from user
0N/A * space into device space (gradientTransform should be
0N/A * concatenated with this)
0N/A * @param hints the hints that the context object uses to choose
0N/A * between rendering alternatives
0N/A * @param cx the center X coordinate in user space of the circle defining
0N/A * the gradient. The last color of the gradient is mapped to
0N/A * the perimeter of this circle.
0N/A * @param cy the center Y coordinate in user space of the circle defining
0N/A * the gradient. The last color of the gradient is mapped to
0N/A * the perimeter of this circle.
0N/A * @param r the radius of the circle defining the extents of the
0N/A * color gradient
0N/A * @param fx the X coordinate in user space to which the first color
0N/A * is mapped
0N/A * @param fy the Y coordinate in user space to which the first color
0N/A * is mapped
0N/A * @param fractions the fractions specifying the gradient distribution
0N/A * @param colors the gradient colors
0N/A * @param cycleMethod either NO_CYCLE, REFLECT, or REPEAT
0N/A * @param colorSpace which colorspace to use for interpolation,
0N/A * either SRGB or LINEAR_RGB
0N/A */
0N/A RadialGradientPaintContext(RadialGradientPaint paint,
0N/A ColorModel cm,
0N/A Rectangle deviceBounds,
0N/A Rectangle2D userBounds,
0N/A AffineTransform t,
0N/A RenderingHints hints,
0N/A float cx, float cy,
0N/A float r,
0N/A float fx, float fy,
0N/A float[] fractions,
0N/A Color[] colors,
0N/A CycleMethod cycleMethod,
0N/A ColorSpaceType colorSpace)
0N/A {
0N/A super(paint, cm, deviceBounds, userBounds, t, hints,
0N/A fractions, colors, cycleMethod, colorSpace);
0N/A
0N/A // copy some parameters
0N/A centerX = cx;
0N/A centerY = cy;
0N/A focusX = fx;
0N/A focusY = fy;
0N/A radius = r;
0N/A
0N/A this.isSimpleFocus = (focusX == centerX) && (focusY == centerY);
0N/A this.isNonCyclic = (cycleMethod == CycleMethod.NO_CYCLE);
0N/A
0N/A // for use in the quadractic equation
0N/A radiusSq = radius * radius;
0N/A
0N/A float dX = focusX - centerX;
0N/A float dY = focusY - centerY;
0N/A
0N/A double distSq = (dX * dX) + (dY * dY);
0N/A
0N/A // test if distance from focus to center is greater than the radius
0N/A if (distSq > radiusSq * SCALEBACK) {
0N/A // clamp focus to radius
0N/A float scalefactor = (float)Math.sqrt(radiusSq * SCALEBACK / distSq);
0N/A dX = dX * scalefactor;
0N/A dY = dY * scalefactor;
0N/A focusX = centerX + dX;
0N/A focusY = centerY + dY;
0N/A }
0N/A
0N/A // calculate the solution to be used in the case where X == focusX
0N/A // in cyclicCircularGradientFillRaster()
0N/A trivial = (float)Math.sqrt(radiusSq - (dX * dX));
0N/A
0N/A // constant parts of X, Y user space coordinates
0N/A constA = a02 - centerX;
0N/A constB = a12 - centerY;
0N/A
0N/A // constant second order delta for simple loop
0N/A gDeltaDelta = 2 * ( a00 * a00 + a10 * a10) / radiusSq;
0N/A }
0N/A
0N/A /**
0N/A * Return a Raster containing the colors generated for the graphics
0N/A * operation.
0N/A *
0N/A * @param x,y,w,h the area in device space for which colors are
0N/A * generated.
0N/A */
0N/A protected void fillRaster(int pixels[], int off, int adjust,
0N/A int x, int y, int w, int h)
0N/A {
0N/A if (isSimpleFocus && isNonCyclic && isSimpleLookup) {
0N/A simpleNonCyclicFillRaster(pixels, off, adjust, x, y, w, h);
0N/A } else {
0N/A cyclicCircularGradientFillRaster(pixels, off, adjust, x, y, w, h);
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * This code works in the simplest of cases, where the focus == center
0N/A * point, the gradient is noncyclic, and the gradient lookup method is
0N/A * fast (single array index, no conversion necessary).
0N/A */
0N/A private void simpleNonCyclicFillRaster(int pixels[], int off, int adjust,
0N/A int x, int y, int w, int h)
0N/A {
0N/A /* We calculate sqrt(X^2 + Y^2) relative to the radius
0N/A * size to get the fraction for the color to use.
0N/A *
0N/A * Each step along the scanline adds (a00, a10) to (X, Y).
0N/A * If we precalculate:
0N/A * gRel = X^2+Y^2
0N/A * for the start of the row, then for each step we need to
0N/A * calculate:
0N/A * gRel' = (X+a00)^2 + (Y+a10)^2
0N/A * = X^2 + 2*X*a00 + a00^2 + Y^2 + 2*Y*a10 + a10^2
0N/A * = (X^2+Y^2) + 2*(X*a00+Y*a10) + (a00^2+a10^2)
0N/A * = gRel + 2*(X*a00+Y*a10) + (a00^2+a10^2)
0N/A * = gRel + 2*DP + SD
0N/A * (where DP = dot product between X,Y and a00,a10
0N/A * and SD = dot product square of the delta vector)
0N/A * For the step after that we get:
0N/A * gRel'' = (X+2*a00)^2 + (Y+2*a10)^2
0N/A * = X^2 + 4*X*a00 + 4*a00^2 + Y^2 + 4*Y*a10 + 4*a10^2
0N/A * = (X^2+Y^2) + 4*(X*a00+Y*a10) + 4*(a00^2+a10^2)
0N/A * = gRel + 4*DP + 4*SD
0N/A * = gRel' + 2*DP + 3*SD
0N/A * The increment changed by:
0N/A * (gRel'' - gRel') - (gRel' - gRel)
0N/A * = (2*DP + 3*SD) - (2*DP + SD)
0N/A * = 2*SD
0N/A * Note that this value depends only on the (inverse of the)
0N/A * transformation matrix and so is a constant for the loop.
0N/A * To make this all relative to the unit circle, we need to
0N/A * divide all values as follows:
0N/A * [XY] /= radius
0N/A * gRel /= radiusSq
0N/A * DP /= radiusSq
0N/A * SD /= radiusSq
0N/A */
0N/A // coordinates of UL corner in "user space" relative to center
0N/A float rowX = (a00*x) + (a01*y) + constA;
0N/A float rowY = (a10*x) + (a11*y) + constB;
0N/A
0N/A // second order delta calculated in constructor
0N/A float gDeltaDelta = this.gDeltaDelta;
0N/A
0N/A // adjust is (scan-w) of pixels array, we need (scan)
0N/A adjust += w;
0N/A
0N/A // rgb of the 1.0 color used when the distance exceeds gradient radius
0N/A int rgbclip = gradient[fastGradientArraySize];
0N/A
0N/A for (int j = 0; j < h; j++) {
0N/A // these values depend on the coordinates of the start of the row
0N/A float gRel = (rowX * rowX + rowY * rowY) / radiusSq;
0N/A float gDelta = (2 * ( a00 * rowX + a10 * rowY) / radiusSq +
0N/A gDeltaDelta/2);
0N/A
0N/A /* Use optimized loops for any cases where gRel >= 1.
0N/A * We do not need to calculate sqrt(gRel) for these
0N/A * values since sqrt(N>=1) == (M>=1).
0N/A * Note that gRel follows a parabola which can only be < 1
0N/A * for a small region around the center on each scanline. In
0N/A * particular:
0N/A * gDeltaDelta is always positive
0N/A * gDelta is <0 until it crosses the midpoint, then >0
0N/A * To the left and right of that region, it will always be
0N/A * >=1 out to infinity, so we can process the line in 3
0N/A * regions:
0N/A * out to the left - quick fill until gRel < 1, updating gRel
0N/A * in the heart - slow fraction=sqrt fill while gRel < 1
0N/A * out to the right - quick fill rest of scanline, ignore gRel
0N/A */
0N/A int i = 0;
0N/A // Quick fill for "out to the left"
0N/A while (i < w && gRel >= 1.0f) {
0N/A pixels[off + i] = rgbclip;
0N/A gRel += gDelta;
0N/A gDelta += gDeltaDelta;
0N/A i++;
0N/A }
0N/A // Slow fill for "in the heart"
0N/A while (i < w && gRel < 1.0f) {
0N/A int gIndex;
0N/A
0N/A if (gRel <= 0) {
0N/A gIndex = 0;
0N/A } else {
0N/A float fIndex = gRel * SQRT_LUT_SIZE;
0N/A int iIndex = (int) (fIndex);
0N/A float s0 = sqrtLut[iIndex];
0N/A float s1 = sqrtLut[iIndex+1] - s0;
0N/A fIndex = s0 + (fIndex - iIndex) * s1;
0N/A gIndex = (int) (fIndex * fastGradientArraySize);
0N/A }
0N/A
0N/A // store the color at this point
0N/A pixels[off + i] = gradient[gIndex];
0N/A
0N/A // incremental calculation
0N/A gRel += gDelta;
0N/A gDelta += gDeltaDelta;
0N/A i++;
0N/A }
0N/A // Quick fill to end of line for "out to the right"
0N/A while (i < w) {
0N/A pixels[off + i] = rgbclip;
0N/A i++;
0N/A }
0N/A
0N/A off += adjust;
0N/A rowX += a01;
0N/A rowY += a11;
0N/A }
0N/A }
0N/A
0N/A // SQRT_LUT_SIZE must be a power of 2 for the test above to work.
0N/A private static final int SQRT_LUT_SIZE = (1 << 11);
0N/A private static float sqrtLut[] = new float[SQRT_LUT_SIZE+1];
0N/A static {
0N/A for (int i = 0; i < sqrtLut.length; i++) {
0N/A sqrtLut[i] = (float) Math.sqrt(i / ((float) SQRT_LUT_SIZE));
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Fill the raster, cycling the gradient colors when a point falls outside
0N/A * of the perimeter of the 100% stop circle.
0N/A *
0N/A * This calculation first computes the intersection point of the line
0N/A * from the focus through the current point in the raster, and the
0N/A * perimeter of the gradient circle.
0N/A *
0N/A * Then it determines the percentage distance of the current point along
0N/A * that line (focus is 0%, perimeter is 100%).
0N/A *
0N/A * Equation of a circle centered at (a,b) with radius r:
0N/A * (x-a)^2 + (y-b)^2 = r^2
0N/A * Equation of a line with slope m and y-intercept b:
0N/A * y = mx + b
0N/A * Replacing y in the circle equation and solving using the quadratic
0N/A * formula produces the following set of equations. Constant factors have
0N/A * been extracted out of the inner loop.
0N/A */
0N/A private void cyclicCircularGradientFillRaster(int pixels[], int off,
0N/A int adjust,
0N/A int x, int y,
0N/A int w, int h)
0N/A {
0N/A // constant part of the C factor of the quadratic equation
0N/A final double constC =
0N/A -radiusSq + (centerX * centerX) + (centerY * centerY);
0N/A
0N/A // coefficients of the quadratic equation (Ax^2 + Bx + C = 0)
0N/A double A, B, C;
0N/A
0N/A // slope and y-intercept of the focus-perimeter line
0N/A double slope, yintcpt;
0N/A
0N/A // intersection with circle X,Y coordinate
0N/A double solutionX, solutionY;
0N/A
0N/A // constant parts of X, Y coordinates
0N/A final float constX = (a00*x) + (a01*y) + a02;
0N/A final float constY = (a10*x) + (a11*y) + a12;
0N/A
0N/A // constants in inner loop quadratic formula
0N/A final float precalc2 = 2 * centerY;
0N/A final float precalc3 = -2 * centerX;
0N/A
0N/A // value between 0 and 1 specifying position in the gradient
0N/A float g;
0N/A
0N/A // determinant of quadratic formula (should always be > 0)
0N/A float det;
0N/A
0N/A // sq distance from the current point to focus
0N/A float currentToFocusSq;
0N/A
0N/A // sq distance from the intersect point to focus
0N/A float intersectToFocusSq;
0N/A
0N/A // temp variables for change in X,Y squared
0N/A float deltaXSq, deltaYSq;
0N/A
0N/A // used to index pixels array
0N/A int indexer = off;
0N/A
0N/A // incremental index change for pixels array
0N/A int pixInc = w+adjust;
0N/A
0N/A // for every row
0N/A for (int j = 0; j < h; j++) {
0N/A
0N/A // user space point; these are constant from column to column
0N/A float X = (a01*j) + constX;
0N/A float Y = (a11*j) + constY;
0N/A
0N/A // for every column (inner loop begins here)
0N/A for (int i = 0; i < w; i++) {
0N/A
0N/A if (X == focusX) {
0N/A // special case to avoid divide by zero
0N/A solutionX = focusX;
0N/A solutionY = centerY;
0N/A solutionY += (Y > focusY) ? trivial : -trivial;
0N/A } else {
0N/A // slope and y-intercept of the focus-perimeter line
0N/A slope = (Y - focusY) / (X - focusX);
0N/A yintcpt = Y - (slope * X);
0N/A
0N/A // use the quadratic formula to calculate the
0N/A // intersection point
0N/A A = (slope * slope) + 1;
0N/A B = precalc3 + (-2 * slope * (centerY - yintcpt));
0N/A C = constC + (yintcpt* (yintcpt - precalc2));
0N/A
0N/A det = (float)Math.sqrt((B * B) - (4 * A * C));
0N/A solutionX = -B;
0N/A
0N/A // choose the positive or negative root depending
0N/A // on where the X coord lies with respect to the focus
0N/A solutionX += (X < focusX)? -det : det;
0N/A solutionX = solutionX / (2 * A); // divisor
0N/A solutionY = (slope * solutionX) + yintcpt;
0N/A }
0N/A
0N/A // Calculate the square of the distance from the current point
0N/A // to the focus and the square of the distance from the
0N/A // intersection point to the focus. Want the squares so we can
0N/A // do 1 square root after division instead of 2 before.
0N/A
0N/A deltaXSq = X - focusX;
0N/A deltaXSq = deltaXSq * deltaXSq;
0N/A
0N/A deltaYSq = Y - focusY;
0N/A deltaYSq = deltaYSq * deltaYSq;
0N/A
0N/A currentToFocusSq = deltaXSq + deltaYSq;
0N/A
0N/A deltaXSq = (float)solutionX - focusX;
0N/A deltaXSq = deltaXSq * deltaXSq;
0N/A
0N/A deltaYSq = (float)solutionY - focusY;
0N/A deltaYSq = deltaYSq * deltaYSq;
0N/A
0N/A intersectToFocusSq = deltaXSq + deltaYSq;
0N/A
0N/A // get the percentage (0-1) of the current point along the
0N/A // focus-circumference line
0N/A g = (float)Math.sqrt(currentToFocusSq / intersectToFocusSq);
0N/A
0N/A // store the color at this point
0N/A pixels[indexer + i] = indexIntoGradientsArrays(g);
0N/A
0N/A // incremental change in X, Y
0N/A X += a00;
0N/A Y += a10;
0N/A } //end inner loop
0N/A
0N/A indexer += pixInc;
0N/A } //end outer loop
0N/A }
0N/A}