MultipleGradientPaintContextpublic abstract class MultipleGradientPaintContext extends Object implements PaintContextThis is the superclass for all PaintContexts which use a multiple color
gradient to fill in their raster. It provides the actual color
interpolation functionality. Subclasses only have to deal with using
the gradient to fill pixels in a raster. |
Fields Summary |
---|
protected ColorModel | modelThe PaintContext's ColorModel. This is ARGB if colors are not all
opaque, otherwise it is RGB. | private static ColorModel | xrgbmodelColor model used if gradient colors are all opaque. | protected static ColorModel | cachedModelThe cached ColorModel. | protected static WeakReference | cachedThe cached raster, which is reusable among instances. | protected Raster | savedRaster is reused whenever possible. | protected java.awt.MultipleGradientPaint.CycleMethod | cycleMethodThe method to use when painting out of the gradient bounds. | protected java.awt.MultipleGradientPaint.ColorSpaceType | colorSpaceThe ColorSpace in which to perform the interpolation | protected float | a00Elements of the inverse transform matrix. | protected float | a01 | protected float | a10 | protected float | a11 | protected float | a02 | protected float | a12 | protected boolean | isSimpleLookupThis boolean specifies wether we are in simple lookup mode, where an
input value between 0 and 1 may be used to directly index into a single
array of gradient colors. If this boolean value is false, then we have
to use a 2-step process where we have to determine which gradient array
we fall into, then determine the index into that array. | protected int | fastGradientArraySizeSize of gradients array for scaling the 0-1 index when looking up
colors the fast way. | protected int[] | gradientArray which contains the interpolated color values for each interval,
used by calculateSingleArrayGradient(). It is protected for possible
direct access by subclasses. | private int[] | gradientsArray of gradient arrays, one array for each interval. Used by
calculateMultipleArrayGradient(). | private float[] | normalizedIntervalsNormalized intervals array. | private float[] | fractionsFractions array. | private int | transparencyTestUsed to determine if gradient colors are all opaque. | private static final int[] | SRGBtoLinearRGBColor space conversion lookup tables. | private static final int[] | LinearRGBtoSRGB | protected static final int | GRADIENT_SIZEConstant number of max colors between any 2 arbitrary colors.
Used for creating and indexing gradients arrays. | protected static final int | GRADIENT_SIZE_INDEX | private static final int | MAX_GRADIENT_ARRAY_SIZEMaximum length of the fast single-array. If the estimated array size
is greater than this, switch over to the slow lookup method.
No particular reason for choosing this number, but it seems to provide
satisfactory performance for the common case (fast lookup). |
Constructors Summary |
---|
protected MultipleGradientPaintContext(MultipleGradientPaint mgp, ColorModel cm, Rectangle deviceBounds, Rectangle2D userBounds, AffineTransform t, RenderingHints hints, float[] fractions, Color[] colors, java.awt.MultipleGradientPaint.CycleMethod cycleMethod, java.awt.MultipleGradientPaint.ColorSpaceType colorSpace)Constructor for MultipleGradientPaintContext superclass.
if (deviceBounds == null) {
throw new NullPointerException("Device bounds cannot be null");
}
if (userBounds == null) {
throw new NullPointerException("User bounds cannot be null");
}
if (t == null) {
throw new NullPointerException("Transform cannot be null");
}
if (hints == null) {
throw new NullPointerException("RenderingHints cannot be null");
}
// The inverse transform is needed to go from device to user space.
// Get all the components of the inverse transform matrix.
AffineTransform tInv;
try {
// the following assumes that the caller has copied the incoming
// transform and is not concerned about it being modified
t.invert();
tInv = t;
} catch (NoninvertibleTransformException e) {
// just use identity transform in this case; better to show
// (incorrect) results than to throw an exception and/or no-op
tInv = new AffineTransform();
}
double m[] = new double[6];
tInv.getMatrix(m);
a00 = (float)m[0];
a10 = (float)m[1];
a01 = (float)m[2];
a11 = (float)m[3];
a02 = (float)m[4];
a12 = (float)m[5];
// copy some flags
this.cycleMethod = cycleMethod;
this.colorSpace = colorSpace;
// we can avoid copying this array since we do not modify its values
this.fractions = fractions;
// note that only one of these values can ever be non-null (we either
// store the fast gradient array or the slow one, but never both
// at the same time)
int[] gradient =
(mgp.gradient != null) ? mgp.gradient.get() : null;
int[][] gradients =
(mgp.gradients != null) ? mgp.gradients.get() : null;
if (gradient == null && gradients == null) {
// we need to (re)create the appropriate values
calculateLookupData(colors);
// now cache the calculated values in the
// MultipleGradientPaint instance for future use
mgp.model = this.model;
mgp.normalizedIntervals = this.normalizedIntervals;
mgp.isSimpleLookup = this.isSimpleLookup;
if (isSimpleLookup) {
// only cache the fast array
mgp.fastGradientArraySize = this.fastGradientArraySize;
mgp.gradient = new SoftReference<int[]>(this.gradient);
} else {
// only cache the slow array
mgp.gradients = new SoftReference<int[][]>(this.gradients);
}
} else {
// use the values cached in the MultipleGradientPaint instance
this.model = mgp.model;
this.normalizedIntervals = mgp.normalizedIntervals;
this.isSimpleLookup = mgp.isSimpleLookup;
this.gradient = gradient;
this.fastGradientArraySize = mgp.fastGradientArraySize;
this.gradients = gradients;
}
|
Methods Summary |
---|
private void | calculateLookupData(java.awt.Color[] colors)This function is the meat of this class. It calculates an array of
gradient colors based on an array of fractions and color values at
those fractions.
Color[] normalizedColors;
if (colorSpace == ColorSpaceType.LINEAR_RGB) {
// create a new colors array
normalizedColors = new Color[colors.length];
// convert the colors using the lookup table
for (int i = 0; i < colors.length; i++) {
int argb = colors[i].getRGB();
int a = argb >>> 24;
int r = SRGBtoLinearRGB[(argb >> 16) & 0xff];
int g = SRGBtoLinearRGB[(argb >> 8) & 0xff];
int b = SRGBtoLinearRGB[(argb ) & 0xff];
normalizedColors[i] = new Color(r, g, b, a);
}
} else {
// we can just use this array by reference since we do not
// modify its values in the case of SRGB
normalizedColors = colors;
}
// this will store the intervals (distances) between gradient stops
normalizedIntervals = new float[fractions.length-1];
// convert from fractions into intervals
for (int i = 0; i < normalizedIntervals.length; i++) {
// interval distance is equal to the difference in positions
normalizedIntervals[i] = this.fractions[i+1] - this.fractions[i];
}
// initialize to be fully opaque for ANDing with colors
transparencyTest = 0xff000000;
// array of interpolation arrays
gradients = new int[normalizedIntervals.length][];
// find smallest interval
float Imin = 1;
for (int i = 0; i < normalizedIntervals.length; i++) {
Imin = (Imin > normalizedIntervals[i]) ?
normalizedIntervals[i] : Imin;
}
// Estimate the size of the entire gradients array.
// This is to prevent a tiny interval from causing the size of array
// to explode. If the estimated size is too large, break to using
// separate arrays for each interval, and using an indexing scheme at
// look-up time.
int estimatedSize = 0;
for (int i = 0; i < normalizedIntervals.length; i++) {
estimatedSize += (normalizedIntervals[i]/Imin) * GRADIENT_SIZE;
}
if (estimatedSize > MAX_GRADIENT_ARRAY_SIZE) {
// slow method
calculateMultipleArrayGradient(normalizedColors);
} else {
// fast method
calculateSingleArrayGradient(normalizedColors, Imin);
}
// use the most "economical" model
if ((transparencyTest >>> 24) == 0xff) {
model = xrgbmodel;
} else {
model = ColorModel.getRGBdefault();
}
| private void | calculateMultipleArrayGradient(java.awt.Color[] colors)SLOW LOOKUP METHOD
This method calculates the gradient color values for each interval and
places each into its own 255 size array. The arrays are stored in
gradients[][]. (255 is used because this is the maximum number of
unique colors between 2 arbitrary colors in a 24 bit color system.)
This method uses the minimum amount of space (only 255 * number of
intervals), but it aggravates the lookup procedure, because now we
have to find out which interval to select, then calculate the index
within that interval. This causes a significant performance hit,
because it requires this calculation be done for every point in
the rendering loop.
For those of you who are interested, this is a classic example of the
time-space tradeoff.
// set the flag so we know later it is a non-simple lookup
isSimpleLookup = false;
// 2 colors to interpolate
int rgb1, rgb2;
// for every interval (transition between 2 colors)
for (int i = 0; i < gradients.length; i++){
// create an array of the maximum theoretical size for
// each interval
gradients[i] = new int[GRADIENT_SIZE];
// get the the 2 colors
rgb1 = colors[i].getRGB();
rgb2 = colors[i+1].getRGB();
// fill this array with the colors in between rgb1 and rgb2
interpolate(rgb1, rgb2, gradients[i]);
// if the colors are opaque, transparency should still
// be 0xff000000
transparencyTest &= rgb1;
transparencyTest &= rgb2;
}
// if interpolation occurred in Linear RGB space, convert the
// gradients back to SRGB using the lookup table
if (colorSpace == ColorSpaceType.LINEAR_RGB) {
for (int j = 0; j < gradients.length; j++) {
for (int i = 0; i < gradients[j].length; i++) {
gradients[j][i] =
convertEntireColorLinearRGBtoSRGB(gradients[j][i]);
}
}
}
| private void | calculateSingleArrayGradient(java.awt.Color[] colors, float Imin)FAST LOOKUP METHOD
This method calculates the gradient color values and places them in a
single int array, gradient[]. It does this by allocating space for
each interval based on its size relative to the smallest interval in
the array. The smallest interval is allocated 255 interpolated values
(the maximum number of unique in-between colors in a 24 bit color
system), and all other intervals are allocated
size = (255 * the ratio of their size to the smallest interval).
This scheme expedites a speedy retrieval because the colors are
distributed along the array according to their user-specified
distribution. All that is needed is a relative index from 0 to 1.
The only problem with this method is that the possibility exists for
the array size to balloon in the case where there is a
disproportionately small gradient interval. In this case the other
intervals will be allocated huge space, but much of that data is
redundant. We thus need to use the space conserving scheme below.
// set the flag so we know later it is a simple (fast) lookup
isSimpleLookup = true;
// 2 colors to interpolate
int rgb1, rgb2;
//the eventual size of the single array
int gradientsTot = 1;
// for every interval (transition between 2 colors)
for (int i = 0; i < gradients.length; i++) {
// create an array whose size is based on the ratio to the
// smallest interval
int nGradients = (int)((normalizedIntervals[i]/Imin)*255f);
gradientsTot += nGradients;
gradients[i] = new int[nGradients];
// the 2 colors (keyframes) to interpolate between
rgb1 = colors[i].getRGB();
rgb2 = colors[i+1].getRGB();
// fill this array with the colors in between rgb1 and rgb2
interpolate(rgb1, rgb2, gradients[i]);
// if the colors are opaque, transparency should still
// be 0xff000000
transparencyTest &= rgb1;
transparencyTest &= rgb2;
}
// put all gradients in a single array
gradient = new int[gradientsTot];
int curOffset = 0;
for (int i = 0; i < gradients.length; i++){
System.arraycopy(gradients[i], 0, gradient,
curOffset, gradients[i].length);
curOffset += gradients[i].length;
}
gradient[gradient.length-1] = colors[colors.length-1].getRGB();
// if interpolation occurred in Linear RGB space, convert the
// gradients back to sRGB using the lookup table
if (colorSpace == ColorSpaceType.LINEAR_RGB) {
for (int i = 0; i < gradient.length; i++) {
gradient[i] = convertEntireColorLinearRGBtoSRGB(gradient[i]);
}
}
fastGradientArraySize = gradient.length - 1;
| private int | convertEntireColorLinearRGBtoSRGB(int rgb)Yet another helper function. This one extracts the color components
of an integer RGB triple, converts them from LinearRGB to SRGB, then
recompacts them into an int.
// color components
int a1, r1, g1, b1;
// extract red, green, blue components
a1 = (rgb >> 24) & 0xff;
r1 = (rgb >> 16) & 0xff;
g1 = (rgb >> 8) & 0xff;
b1 = (rgb ) & 0xff;
// use the lookup table
r1 = LinearRGBtoSRGB[r1];
g1 = LinearRGBtoSRGB[g1];
b1 = LinearRGBtoSRGB[b1];
// re-compact the components
return ((a1 << 24) |
(r1 << 16) |
(g1 << 8) |
(b1 ));
| private static int | convertLinearRGBtoSRGB(int color)Helper function to convert a color component in linear RGB space to
SRGB space. Used to build a static lookup table.
float input, output;
input = color/255.0f;
if (input <= 0.0031308) {
output = input * 12.92f;
} else {
output = (1.055f *
((float) Math.pow(input, (1.0 / 2.4)))) - 0.055f;
}
return Math.round(output * 255.0f);
| private static int | convertSRGBtoLinearRGB(int color)Helper function to convert a color component in sRGB space to linear
RGB space. Used to build a static lookup table.
float input, output;
input = color / 255.0f;
if (input <= 0.04045f) {
output = input / 12.92f;
} else {
output = (float)Math.pow((input + 0.055) / 1.055, 2.4);
}
return Math.round(output * 255.0f);
| public final void | dispose(){@inheritDoc}
if (saved != null) {
putCachedRaster(model, saved);
saved = null;
}
| protected abstract void | fillRaster(int[] pixels, int off, int adjust, int x, int y, int w, int h)
| private static synchronized java.awt.image.Raster | getCachedRaster(java.awt.image.ColorModel cm, int w, int h)Took this cacheRaster code from GradientPaint. It appears to recycle
rasters for use by any other instance, as long as they are sufficiently
large.
if (cm == cachedModel) {
if (cached != null) {
Raster ras = (Raster) cached.get();
if (ras != null &&
ras.getWidth() >= w &&
ras.getHeight() >= h)
{
cached = null;
return ras;
}
}
}
return cm.createCompatibleWritableRaster(w, h);
| public final java.awt.image.ColorModel | getColorModel(){@inheritDoc}
return model;
| public final java.awt.image.Raster | getRaster(int x, int y, int w, int h){@inheritDoc}
// If working raster is big enough, reuse it. Otherwise,
// build a large enough new one.
Raster raster = saved;
if (raster == null ||
raster.getWidth() < w || raster.getHeight() < h)
{
raster = getCachedRaster(model, w, h);
saved = raster;
}
// Access raster internal int array. Because we use a DirectColorModel,
// we know the DataBuffer is of type DataBufferInt and the SampleModel
// is SinglePixelPackedSampleModel.
// Adjust for initial offset in DataBuffer and also for the scanline
// stride.
DataBufferInt rasterDB = (DataBufferInt)raster.getDataBuffer();
int[] pixels = rasterDB.getBankData()[0];
int off = rasterDB.getOffset();
int scanlineStride = ((SinglePixelPackedSampleModel)
raster.getSampleModel()).getScanlineStride();
int adjust = scanlineStride - w;
fillRaster(pixels, off, adjust, x, y, w, h); // delegate to subclass
return raster;
| protected final int | indexIntoGradientsArrays(float position)Helper function to index into the gradients array. This is necessary
because each interval has an array of colors with uniform size 255.
However, the color intervals are not necessarily of uniform length, so
a conversion is required.
// first, manipulate position value depending on the cycle method
if (cycleMethod == CycleMethod.NO_CYCLE) {
if (position > 1) {
// upper bound is 1
position = 1;
} else if (position < 0) {
// lower bound is 0
position = 0;
}
} else if (cycleMethod == CycleMethod.REPEAT) {
// get the fractional part
// (modulo behavior discards integer component)
position = position - (int)position;
//position should now be between -1 and 1
if (position < 0) {
// force it to be in the range 0-1
position = position + 1;
}
} else { // cycleMethod == CycleMethod.REFLECT
if (position < 0) {
// take absolute value
position = -position;
}
// get the integer part
int part = (int)position;
// get the fractional part
position = position - part;
if ((part & 1) == 1) {
// integer part is odd, get reflected color instead
position = 1 - position;
}
}
// now, get the color based on this 0-1 position...
if (isSimpleLookup) {
// easy to compute: just scale index by array size
return gradient[(int)(position * fastGradientArraySize)];
} else {
// more complicated computation, to save space
// for all the gradient interval arrays
for (int i = 0; i < gradients.length; i++) {
if (position < fractions[i+1]) {
// this is the array we want
float delta = position - fractions[i];
// this is the interval we want
int index = (int)((delta / normalizedIntervals[i])
* (GRADIENT_SIZE_INDEX));
return gradients[i][index];
}
}
}
return gradients[gradients.length - 1][GRADIENT_SIZE_INDEX];
| private void | interpolate(int rgb1, int rgb2, int[] output)Yet another helper function. This one linearly interpolates between
2 colors, filling up the output array.
// color components
int a1, r1, g1, b1, da, dr, dg, db;
// step between interpolated values
float stepSize = 1.0f / output.length;
// extract color components from packed integer
a1 = (rgb1 >> 24) & 0xff;
r1 = (rgb1 >> 16) & 0xff;
g1 = (rgb1 >> 8) & 0xff;
b1 = (rgb1 ) & 0xff;
// calculate the total change in alpha, red, green, blue
da = ((rgb2 >> 24) & 0xff) - a1;
dr = ((rgb2 >> 16) & 0xff) - r1;
dg = ((rgb2 >> 8) & 0xff) - g1;
db = ((rgb2 ) & 0xff) - b1;
// for each step in the interval calculate the in-between color by
// multiplying the normalized current position by the total color
// change (0.5 is added to prevent truncation round-off error)
for (int i = 0; i < output.length; i++) {
output[i] =
(((int) ((a1 + i * da * stepSize) + 0.5) << 24)) |
(((int) ((r1 + i * dr * stepSize) + 0.5) << 16)) |
(((int) ((g1 + i * dg * stepSize) + 0.5) << 8)) |
(((int) ((b1 + i * db * stepSize) + 0.5) ));
}
| private static synchronized void | putCachedRaster(java.awt.image.ColorModel cm, java.awt.image.Raster ras)Took this cacheRaster code from GradientPaint. It appears to recycle
rasters for use by any other instance, as long as they are sufficiently
large.
if (cached != null) {
Raster cras = (Raster) cached.get();
if (cras != null) {
int cw = cras.getWidth();
int ch = cras.getHeight();
int iw = ras.getWidth();
int ih = ras.getHeight();
if (cw >= iw && ch >= ih) {
return;
}
if (cw * ch >= iw * ih) {
return;
}
}
}
cachedModel = cm;
cached = new WeakReference<Raster>(ras);
|
|