/prism-j2d/src/com/sun/prism/j2d/paint/RadialGradientPaintContext.java
Java | 466 lines | 193 code | 65 blank | 208 comment | 29 complexity | acfc672ed2505986db085396fe3f38ba MD5 | raw file
Possible License(s): BSD-3-Clause, LGPL-2.1, GPL-2.0, LGPL-2.0
- /*
- * 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.j2d.paint;
- import com.sun.prism.j2d.paint.MultipleGradientPaint.CycleMethod;
- import com.sun.prism.j2d.paint.MultipleGradientPaint.ColorSpaceType;
- import java.awt.Color;
- import java.awt.Rectangle;
- import java.awt.RenderingHints;
- import java.awt.geom.AffineTransform;
- import java.awt.geom.Rectangle2D;
- import java.awt.image.ColorModel;
- /**
- * 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 RadialGradientPaintContext extends MultipleGradientPaintContext {
- /** True when (focus == center). */
- private boolean isSimpleFocus = false;
- /** True when (cycleMethod == NO_CYCLE). */
- private boolean isNonCyclic = false;
- /** Radius of the outermost circle defining the 100% gradient stop. */
- private float radius;
- /** Variables representing center and focus points. */
- private float centerX, centerY, focusX, focusY;
- /** Radius of the gradient circle squared. */
- private float radiusSq;
- /** Constant part of X, Y user space coordinates. */
- private float constA, constB;
- /** 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.
- */
- private float trivial;
- /** Amount for offset when clamping focus. */
- private static final float SCALEBACK = .99f;
- /**
- * Constructor for RadialGradientPaintContext.
- *
- * @param paint the {@code RadialGradientPaint} from which this context
- * is created
- * @param cm the {@code ColorModel} that receives
- * the {@code Paint} data (this is used only as a hint)
- * @param deviceBounds the device space bounding box of the
- * graphics primitive being rendered
- * @param userBounds the user space bounding box of the
- * graphics primitive being rendered
- * @param t the {@code AffineTransform} from user
- * space into device space (gradientTransform should be
- * concatenated with this)
- * @param hints the hints that the context object uses to choose
- * between rendering alternatives
- * @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
- * @param colorSpace which colorspace to use for interpolation,
- * either SRGB or LINEAR_RGB
- */
- RadialGradientPaintContext(RadialGradientPaint paint,
- ColorModel cm,
- Rectangle deviceBounds,
- Rectangle2D userBounds,
- AffineTransform t,
- RenderingHints hints,
- float cx, float cy,
- float r,
- float fx, float fy,
- float[] fractions,
- Color[] colors,
- CycleMethod cycleMethod,
- ColorSpaceType colorSpace)
- {
- super(paint, cm, deviceBounds, userBounds, t, hints,
- fractions, colors, cycleMethod, colorSpace);
- // copy some parameters
- centerX = cx;
- centerY = cy;
- focusX = fx;
- focusY = fy;
- radius = r;
- this.isSimpleFocus = (focusX == centerX) && (focusY == centerY);
- this.isNonCyclic = (cycleMethod == CycleMethod.NO_CYCLE);
- // 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.
- *
- * @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).
- */
- 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.
- */
- 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
- if (trivial == 0) {
- // Optimization for case where we will calculate
- // exceptional values for nearly every pixel below.
- int rgb0 = indexIntoGradientsArrays(0f);
- for (int j = 0; j < h; j++) {
- for (int i = 0; i < w; i++) {
- pixels[indexer + i] = rgb0;
- }
- indexer += pixInc;
- }
- return;
- }
- 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;
- if (intersectToFocusSq == 0) {
- intersectToFocusSq =
- (solutionY >= focusY) ? trivial : -trivial;
- }
- // 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
- }
- }