/*****************************************************************************
* Copyright (C) The Apache Software Foundation. All rights reserved. *
* ------------------------------------------------------------------------- *
* This software is published under the terms of the Apache Software License *
* version 1.1, a copy of which has been included with this distribution in *
* the LICENSE file. *
*****************************************************************************/
package com.kitfox.svg.batik;
import java.awt.Color;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException;
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
* @author <a href="mailto:vincent.hardy@eng.sun.com">Vincent Hardy</a>
* @version $Id: RadialGradientPaintContext.java,v 1.2 2005/10/12 20:36:55
* kitfox Exp $
*
*/
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;
/**
* 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;
private static final int FIXED_POINT_IMPL = 1;
private static final int DEFAULT_IMPL = 2;
private static final int ANTI_ALIAS_IMPL = 3;
private int fillMethod;
/** Amount for offset when clamping focus. */
private static final float SCALEBACK = .97f;
/**
* Constructor for RadialGradientPaintContext.
*
* @param cm
* {@link ColorModel} that receives the <code>Paint</code> 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 {@link 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 point in user space of the circle defining the
* gradient. The last color of the gradient is mapped to the
* perimeter of this circle X coordinate
*
* @param cy
* the center point in user space of the circle defining the
* gradient. The last color of the gradient is mapped to the
* perimeter of this circle Y coordinate
*
* @param r
* the radius of the circle defining the extents of the color
* gradient
*
* @param fx
* the point in user space to which the first color is mapped X
* coordinate
*
* @param fy
* the point in user space to which the first color is mapped Y
* coordinate
*
* @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
*
*/
public RadialGradientPaintContext(ColorModel cm, Rectangle deviceBounds,
Rectangle2D userBounds, AffineTransform t, RenderingHints hints,
float cx, float cy, float r, float fx, float fy, float[] fractions,
Color[] colors, MultipleGradientPaint.CycleMethodEnum cycleMethod,
MultipleGradientPaint.ColorSpaceEnum colorSpace)
throws NoninvertibleTransformException {
super(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 == RadialGradientPaint.NO_CYCLE);
// for use in the quadractic equation
radiusSq = radius * radius;
float dX = focusX - centerX;
float dY = focusY - centerY;
double dist = Math.sqrt((dX * dX) + (dY * dY));
// test if distance from focus to center is greater than the radius
if (dist > radius * SCALEBACK) { // clamp focus to radius
double angle = Math.atan2(dY, dX);
// x = r cos theta, y = r sin theta
focusX = (float) (SCALEBACK * radius * Math.cos(angle)) + centerX;
focusY = (float) (SCALEBACK * radius * Math.sin(angle)) + centerY;
}
// calculate the solution to be used in the case where X == focusX
// in cyclicCircularGradientFillRaster
dX = focusX - centerX;
trivial = (float) Math.sqrt(radiusSq - (dX * dX));
// constant parts of X, Y user space coordinates
constA = a02 - centerX;
constB = a12 - centerY;
Object colorRend;
Object rend;
// hints can be null on Mac OSX
if (hints == null) {
colorRend = RenderingHints.VALUE_COLOR_RENDER_DEFAULT;
rend = RenderingHints.VALUE_RENDER_DEFAULT;
} else {
colorRend = hints.get(RenderingHints.KEY_COLOR_RENDERING);
rend = hints.get(RenderingHints.KEY_RENDERING);
}
fillMethod = 0;
if ((rend == RenderingHints.VALUE_RENDER_QUALITY)
|| (colorRend == RenderingHints.VALUE_COLOR_RENDER_QUALITY)) {
// System.out.println("AAHints set: " + rend + ", " + colorRend);
fillMethod = ANTI_ALIAS_IMPL;
}
if ((rend == RenderingHints.VALUE_RENDER_SPEED)
|| (colorRend == RenderingHints.VALUE_COLOR_RENDER_SPEED)) {
// System.out.println("SPHints set: " + rend + ", " + colorRend);
fillMethod = DEFAULT_IMPL;
}
// We are in the 'default' case, no hint or hint set to
// DEFAULT values...
if (fillMethod == 0) {
// For now we will always use the 'default' impl if
// one is not specified.
fillMethod = DEFAULT_IMPL;
if (false) {
// This could be used for a 'smart' choice in
// the default case, if the gradient has obvious
// discontinuites use AA, otherwise default
if (hasDiscontinuity) {
fillMethod = ANTI_ALIAS_IMPL;
} else {
fillMethod = DEFAULT_IMPL;
}
}
}
if ((fillMethod == DEFAULT_IMPL)
&& (isSimpleFocus && isNonCyclic && isSimpleLookup)) {
this.calculateFixedPointSqrtLookupTable();
fillMethod = FIXED_POINT_IMPL;
}
}
/**
* 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.
*/
@Override
protected void fillRaster(int pixels[], int off, int adjust, int x, int y,
int w, int h) {
switch (fillMethod) {
case FIXED_POINT_IMPL:
// System.out.println("Calling FP");
fixedPointSimplestCaseNonCyclicFillRaster(pixels, off, adjust, x, y,
w, h);
break;
case ANTI_ALIAS_IMPL:
// System.out.println("Calling AA");
antiAliasFillRaster(pixels, off, adjust, x, y, w, h);
break;
case DEFAULT_IMPL:
default:
// System.out.println("Calling Default");
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 fixedPointSimplestCaseNonCyclicFillRaster(int pixels[],
int off, int adjust, int x, int y, int w, int h) {
float iSq = 0; // Square distance index
final float indexFactor = fastGradientArraySize / radius;
// constant part of X and Y coordinates for the entire raster
final float constX = (a00 * x) + (a01 * y) + constA;
final float constY = (a10 * x) + (a11 * y) + constB;
final float deltaX = indexFactor * a00; // incremental change in dX
final float deltaY = indexFactor * a10; // incremental change in dY
float dX, dY; // the current distance from center
final int fixedArraySizeSq = (fastGradientArraySize
* fastGradientArraySize);
float g, gDelta, gDeltaDelta, temp; // gradient square value
int gIndex; // integer number used to index gradient array
int iSqInt; // Square distance index
int end, j; // indexing variables
int indexer = off;// used to index pixels array
temp = ((deltaX * deltaX) + (deltaY * deltaY));
gDeltaDelta = ((temp * 2));
if (temp > fixedArraySizeSq) {
// This combination of scale and circle radius means
// essentially no pixels will be anything but the end
// stop color. This also avoids math problems.
final int val = gradientOverflow;
for (j = 0; j < h; j++) { // for every row
// for every column (inner loop begins here)
for (end = indexer + w; indexer < end; indexer++) {
pixels[indexer] = val;
}
indexer += adjust;
}
return;
}
// For every point in the raster, calculate the color at that point
for (j = 0; j < h; j++) { // for every row
// x and y (in user space) of the first pixel of this row
dX = indexFactor * ((a01 * j) + constX);
dY = indexFactor * ((a11 * j) + constY);
// these values below here allow for an incremental calculation
// of dX^2 + dY^2
// initialize to be equal to distance squared
g = (((dY * dY) + (dX * dX)));
gDelta = (((((deltaY * dY) + (deltaX * dX)) * 2) + temp));
// for every column (inner loop begins here)
for (end = indexer + w; indexer < end; indexer++) {
// determine the distance to the center
// since this is a non cyclic fill raster, crop at "1" and 0
if (g >= fixedArraySizeSq) {
pixels[indexer] = gradientOverflow;
}
// This should not happen as gIndex is a square
// quantity. Code commented out on purpose, can't underflow.
// else if (g < 0) {
// gIndex = 0;
// }
else {
iSq = (g * invSqStepFloat);
iSqInt = (int) iSq; // chop off fractional part
iSq -= iSqInt;
gIndex = sqrtLutFixed[iSqInt];
gIndex += (int) (iSq * (sqrtLutFixed[iSqInt + 1] - gIndex));
pixels[indexer] = gradient[gIndex];
}
// incremental calculation
g += gDelta;
gDelta += gDeltaDelta;
}
indexer += adjust;
}
}
/** Length of a square distance intervale in the lookup table */
private float invSqStepFloat;
/** Used to limit the size of the square root lookup table */
private int MAX_PRECISION = 256;
/** Square root lookup table */
private int sqrtLutFixed[] = new int[MAX_PRECISION];
/**
* Build square root lookup table
*/
private void calculateFixedPointSqrtLookupTable() {
float sqStepFloat;
sqStepFloat = ((fastGradientArraySize * fastGradientArraySize)
/ (MAX_PRECISION - 2));
// The last two values are the same so that linear square root
// interpolation can happen on the maximum reachable element in the
// lookup table (precision-2)
int i;
for (i = 0; i < MAX_PRECISION - 1; i++) {
sqrtLutFixed[i] = (int) (Math.sqrt(i * sqStepFloat));
}
sqrtLutFixed[i] = sqrtLutFixed[i - 1];
invSqStepFloat = 1 / sqStepFloat;
}
/**
* 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 cirlce 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);
double A; // coefficient of the quadratic equation (Ax^2 + Bx + C = 0)
double B; // coefficient of the quadratic equation
double C; // coefficient of the quadratic equation
double slope; // slope of the focus-perimeter line
double yintcpt; // y-intercept of the focus-perimeter line
double solutionX;// intersection with circle X coordinate
double solutionY;// intersection with circle Y coordinate
final float constX = (a00 * x) + (a01 * y) + a02;// const part of X
// coord
final float constY = (a10 * x) + (a11 * y) + a12; // const part of Y
// coord
final float precalc2 = 2 * centerY;// const in inner loop quad. formula
final float precalc3 = -2 * centerX;// const in inner loop quad. formula
float X; // User space point X coordinate
float Y; // User space point Y coordinate
float g;// value between 0 and 1 specifying position in the gradient
float det; // determinant of quadratic formula (should always be >0)
float currentToFocusSq;// sq distance from the current pt. to focus
float intersectToFocusSq;// sq distance from the intersect pt. to focus
float deltaXSq; // temp variable for a change in X squared.
float deltaYSq; // temp variable for a change in Y squared.
int indexer = off; // index variable for pixels array
int i, j; // indexing variables for FOR loops
int pixInc = w + adjust;// incremental index change for pixels array
for (j = 0; j < h; j++) { // for every row
X = (a01 * j) + constX; // constants from column to column
Y = (a11 * j) + constY;
// for every column (inner loop begins here)
for (i = 0; i < w; i++) {
// special case to avoid divide by zero or very near zero
if (((X - focusX) > -0.000001) && ((X - focusX) < 0.000001)) {
solutionX = focusX;
solutionY = centerY;
solutionY += (Y > focusY) ? trivial : -trivial;
}
else {
// slope of the focus-current line
slope = (Y - focusY) / (X - focusX);
yintcpt = Y - (slope * X); // y-intercept of that same line
// 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 = (float) solutionX - focusX;
deltaXSq = deltaXSq * deltaXSq;
deltaYSq = (float) solutionY - focusY;
deltaYSq = deltaYSq * deltaYSq;
intersectToFocusSq = deltaXSq + deltaYSq;
deltaXSq = X - focusX;
deltaXSq = deltaXSq * deltaXSq;
deltaYSq = Y - focusY;
deltaYSq = deltaYSq * deltaYSq;
currentToFocusSq = deltaXSq + deltaYSq;
// want the percentage (0-1) of the current point along the
// focus-circumference line
g = (float) Math.sqrt(currentToFocusSq / intersectToFocusSq);
// Get the color at this point
pixels[indexer + i] = indexIntoGradientsArrays(g);
X += a00; // incremental change in X, Y
Y += a10;
} // end inner loop
indexer += pixInc;
} // end outer loop
}
/**
* Fill the raster, cycling the gradient colors when a point falls outside
* of the perimeter of the 100% stop circle. Use the anti-aliased gradient
* lookup.
*
* 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 cirlce 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 antiAliasFillRaster(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)
final float precalc2 = 2 * centerY;// const in inner loop quad. formula
final float precalc3 = -2 * centerX;// const in inner loop quad. formula
// const part of X,Y coord (shifted to bottom left corner of pixel.
final float constX = (a00 * (x - .5f)) + (a01 * (y + .5f)) + a02;
final float constY = (a10 * (x - .5f)) + (a11 * (y + .5f)) + a12;
float X; // User space point X coordinate
float Y; // User space point Y coordinate
int i, j; // indexing variables for FOR loops
int indexer = off - 1; // index variable for pixels array
double[] prevGs = new double[w + 1];
double deltaXSq, deltaYSq;
double solutionX, solutionY;
double slope, yintcpt, A, B, C, det;
double intersectToFocusSq, currentToFocusSq;
double g00, g01, g10, g11;
// Set X,Y to top left corner of first pixel of first row.
X = constX - a01;
Y = constY - a11;
// Calc top row of g's.
for (i = 0; i <= w; i++) {
// special case to avoid divide by zero or very near zero
if (((X - focusX) > -0.000001) && ((X - focusX) < 0.000001)) {
solutionX = focusX;
solutionY = centerY;
solutionY += (Y > focusY) ? trivial : -trivial;
} else {
// Formula for Circle: (X-Xc)^2 + (Y-Yc)^2 - R^2 = 0
// Formula line: Y = Slope*x + Y0;
//
// So you substitue line into Circle and apply
// Quadradic formula.
// slope of the focus-current line
slope = (Y - focusY) / (X - focusX);
yintcpt = Y - (slope * X); // y-intercept of that same line
// 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 = 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 = solutionX - focusX;
deltaXSq = deltaXSq * deltaXSq;
deltaYSq = solutionY - focusY;
deltaYSq = deltaYSq * deltaYSq;
intersectToFocusSq = deltaXSq + deltaYSq;
deltaXSq = X - focusX;
deltaXSq = deltaXSq * deltaXSq;
deltaYSq = Y - focusY;
deltaYSq = deltaYSq * deltaYSq;
currentToFocusSq = deltaXSq + deltaYSq;
// want the percentage (0-1) of the current point along the
// focus-circumference line
prevGs[i] = Math.sqrt(currentToFocusSq / intersectToFocusSq);
X += a00; // incremental change in X, Y
Y += a10;
}
for (j = 0; j < h; j++) { // for every row
// Set X,Y to bottom edge of pixel row.
X = (a01 * j) + constX; // constants from row to row
Y = (a11 * j) + constY;
g10 = prevGs[0];
// special case to avoid divide by zero or very near zero
if (((X - focusX) > -0.000001) && ((X - focusX) < 0.000001)) {
solutionX = focusX;
solutionY = centerY;
solutionY += (Y > focusY) ? trivial : -trivial;
} else {
// Formula for Circle: (X-Xc)^2 + (Y-Yc)^2 - R^2 = 0
// Formula line: Y = Slope*x + Y0;
//
// So you substitue line into Circle and apply
// Quadradic formula.
// slope of the focus-current line
slope = (Y - focusY) / (X - focusX);
yintcpt = Y - (slope * X); // y-intercept of that same line
// 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 = 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 = solutionX - focusX;
deltaXSq = deltaXSq * deltaXSq;
deltaYSq = solutionY - focusY;
deltaYSq = deltaYSq * deltaYSq;
intersectToFocusSq = deltaXSq + deltaYSq;
deltaXSq = X - focusX;
deltaXSq = deltaXSq * deltaXSq;
deltaYSq = Y - focusY;
deltaYSq = deltaYSq * deltaYSq;
currentToFocusSq = deltaXSq + deltaYSq;
g11 = Math.sqrt(currentToFocusSq / intersectToFocusSq);
prevGs[0] = g11;
X += a00; // incremental change in X, Y
Y += a10;
// for every column (inner loop begins here)
for (i = 1; i <= w; i++) {
g00 = g10;
g01 = g11;
g10 = prevGs[i];
// special case to avoid divide by zero or very near zero
if (((X - focusX) > -0.000001) && ((X - focusX) < 0.000001)) {
solutionX = focusX;
solutionY = centerY;
solutionY += (Y > focusY) ? trivial : -trivial;
} else {
// Formula for Circle: (X-Xc)^2 + (Y-Yc)^2 - R^2 = 0
// Formula line: Y = Slope*x + Y0;
//
// So you substitue line into Circle and apply
// Quadradic formula.
// slope of the focus-current line
slope = (Y - focusY) / (X - focusX);
yintcpt = Y - (slope * X); // y-intercept of that same line
// 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 = 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 = solutionX - focusX;
deltaXSq = deltaXSq * deltaXSq;
deltaYSq = solutionY - focusY;
deltaYSq = deltaYSq * deltaYSq;
intersectToFocusSq = deltaXSq + deltaYSq;
deltaXSq = X - focusX;
deltaXSq = deltaXSq * deltaXSq;
deltaYSq = Y - focusY;
deltaYSq = deltaYSq * deltaYSq;
currentToFocusSq = deltaXSq + deltaYSq;
g11 = Math.sqrt(currentToFocusSq / intersectToFocusSq);
prevGs[i] = g11;
// Get the color at this point
pixels[indexer + i] = indexGradientAntiAlias(
(float) ((g00 + g01 + g10 + g11) / 4), (float) Math
.max(Math.abs(g11 - g00), Math.abs(g10 - g01)));
X += a00; // incremental change in X, Y
Y += a10;
} // end inner loop
indexer += (w + adjust);
} // end outer loop
}
}