/*
* Copyright (c) 2006, Oracle and/or its affiliates. All rights reserved.
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
package java.awt;
import java.awt.
MultipleGradientPaint.
CycleMethod;
import java.awt.
MultipleGradientPaint.
ColorSpaceType;
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.
*
* @author Nicholas Talian, Vincent Hardy, Jim Graham, Jerry Evans
*/
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
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
}
}