1 | /*****************************************************************************
|
---|
2 | * Copyright (C) The Apache Software Foundation. All rights reserved. *
|
---|
3 | * ------------------------------------------------------------------------- *
|
---|
4 | * This software is published under the terms of the Apache Software License *
|
---|
5 | * version 1.1, a copy of which has been included with this distribution in *
|
---|
6 | * the LICENSE file. *
|
---|
7 | *****************************************************************************/
|
---|
8 |
|
---|
9 | package com.kitfox.svg.batik;
|
---|
10 |
|
---|
11 | import java.awt.Color;
|
---|
12 | import java.awt.PaintContext;
|
---|
13 | import java.awt.Rectangle;
|
---|
14 | import java.awt.RenderingHints;
|
---|
15 | import java.awt.color.ColorSpace;
|
---|
16 | import java.awt.geom.AffineTransform;
|
---|
17 | import java.awt.geom.NoninvertibleTransformException;
|
---|
18 | import java.awt.geom.Rectangle2D;
|
---|
19 | import java.awt.image.ColorModel;
|
---|
20 | import java.awt.image.DataBuffer;
|
---|
21 | import java.awt.image.DataBufferInt;
|
---|
22 | import java.awt.image.DirectColorModel;
|
---|
23 | import java.awt.image.Raster;
|
---|
24 | import java.awt.image.SinglePixelPackedSampleModel;
|
---|
25 | import java.awt.image.WritableRaster;
|
---|
26 | import java.lang.ref.WeakReference;
|
---|
27 |
|
---|
28 | //import org.apache.batik.ext.awt.image.GraphicsUtil;
|
---|
29 |
|
---|
30 | /** This is the superclass for all PaintContexts which use a multiple color
|
---|
31 | * gradient to fill in their raster. It provides the actual color interpolation
|
---|
32 | * functionality. Subclasses only have to deal with using the gradient to fill
|
---|
33 | * pixels in a raster.
|
---|
34 | *
|
---|
35 | * @author Nicholas Talian, Vincent Hardy, Jim Graham, Jerry Evans
|
---|
36 | * @author <a href="mailto:vincent.hardy@eng.sun.com">Vincent Hardy</a>
|
---|
37 | * @version $Id: MultipleGradientPaintContext.java,v 1.1 2004/09/06 19:35:39 kitfox Exp $
|
---|
38 | *
|
---|
39 | */
|
---|
40 | abstract class MultipleGradientPaintContext implements PaintContext {
|
---|
41 |
|
---|
42 | protected final static boolean DEBUG = false;
|
---|
43 |
|
---|
44 | /**
|
---|
45 | * The color model data is generated in (always un premult).
|
---|
46 | */
|
---|
47 | protected ColorModel dataModel;
|
---|
48 | /**
|
---|
49 | * PaintContext's output ColorModel ARGB if colors are not all
|
---|
50 | * opaque, RGB otherwise. Linear and premult are matched to
|
---|
51 | * output ColorModel.
|
---|
52 | */
|
---|
53 | protected ColorModel model;
|
---|
54 |
|
---|
55 | /** Color model used if gradient colors are all opaque */
|
---|
56 | private static ColorModel lrgbmodel_NA = new DirectColorModel
|
---|
57 | (ColorSpace.getInstance(ColorSpace.CS_LINEAR_RGB),
|
---|
58 | 24, 0xff0000, 0xFF00, 0xFF, 0x0,
|
---|
59 | false, DataBuffer.TYPE_INT);
|
---|
60 |
|
---|
61 | private static ColorModel srgbmodel_NA = new DirectColorModel
|
---|
62 | (ColorSpace.getInstance(ColorSpace.CS_sRGB),
|
---|
63 | 24, 0xff0000, 0xFF00, 0xFF, 0x0,
|
---|
64 | false, DataBuffer.TYPE_INT);
|
---|
65 |
|
---|
66 | /** Color model used if some gradient colors are transparent */
|
---|
67 | private static ColorModel lrgbmodel_A = new DirectColorModel
|
---|
68 | (ColorSpace.getInstance(ColorSpace.CS_LINEAR_RGB),
|
---|
69 | 32, 0xff0000, 0xFF00, 0xFF, 0xFF000000,
|
---|
70 | false, DataBuffer.TYPE_INT);
|
---|
71 |
|
---|
72 | private static ColorModel srgbmodel_A = new DirectColorModel
|
---|
73 | (ColorSpace.getInstance(ColorSpace.CS_sRGB),
|
---|
74 | 32, 0xff0000, 0xFF00, 0xFF, 0xFF000000,
|
---|
75 | false, DataBuffer.TYPE_INT);
|
---|
76 |
|
---|
77 | /** The cached colorModel */
|
---|
78 | protected static ColorModel cachedModel;
|
---|
79 |
|
---|
80 | /** The cached raster, which is reusable among instances */
|
---|
81 | protected static WeakReference cached;
|
---|
82 |
|
---|
83 | /** Raster is reused whenever possible */
|
---|
84 | protected WritableRaster saved;
|
---|
85 |
|
---|
86 | /** The method to use when painting out of the gradient bounds. */
|
---|
87 | protected MultipleGradientPaint.CycleMethodEnum cycleMethod;
|
---|
88 |
|
---|
89 | /** The colorSpace in which to perform the interpolation */
|
---|
90 | protected MultipleGradientPaint.ColorSpaceEnum colorSpace;
|
---|
91 |
|
---|
92 | /** Elements of the inverse transform matrix. */
|
---|
93 | protected float a00, a01, a10, a11, a02, a12;
|
---|
94 |
|
---|
95 | /** This boolean specifies wether we are in simple lookup mode, where an
|
---|
96 | * input value between 0 and 1 may be used to directly index into a single
|
---|
97 | * array of gradient colors. If this boolean value is false, then we have
|
---|
98 | * to use a 2-step process where we have to determine which gradient array
|
---|
99 | * we fall into, then determine the index into that array.
|
---|
100 | */
|
---|
101 | protected boolean isSimpleLookup = true;
|
---|
102 |
|
---|
103 | /** This boolean indicates if the gradient appears to have sudden
|
---|
104 | * discontinuities in it, this may be because of multiple stops
|
---|
105 | * at the same location or use of the REPEATE mode.
|
---|
106 | */
|
---|
107 | protected boolean hasDiscontinuity = false;
|
---|
108 |
|
---|
109 | /** Size of gradients array for scaling the 0-1 index when looking up
|
---|
110 | * colors the fast way. */
|
---|
111 | protected int fastGradientArraySize;
|
---|
112 |
|
---|
113 | /**
|
---|
114 | * Array which contains the interpolated color values for each interval,
|
---|
115 | * used by calculateSingleArrayGradient(). It is protected for possible
|
---|
116 | * direct access by subclasses.
|
---|
117 | */
|
---|
118 | protected int[] gradient;
|
---|
119 |
|
---|
120 | /** Array of gradient arrays, one array for each interval. Used by
|
---|
121 | * calculateMultipleArrayGradient().
|
---|
122 | */
|
---|
123 | protected int[][] gradients;
|
---|
124 |
|
---|
125 | /** This holds the blend of all colors in the gradient.
|
---|
126 | * we use this at extreamly low resolutions to ensure we
|
---|
127 | * get a decent blend of the colors.
|
---|
128 | */
|
---|
129 | protected int gradientAverage;
|
---|
130 |
|
---|
131 | /** This holds the color to use when we are off the bottom of the
|
---|
132 | * gradient */
|
---|
133 | protected int gradientUnderflow;
|
---|
134 |
|
---|
135 | /** This holds the color to use when we are off the top of the
|
---|
136 | * gradient */
|
---|
137 | protected int gradientOverflow;
|
---|
138 |
|
---|
139 | /** Length of the 2D slow lookup gradients array. */
|
---|
140 | protected int gradientsLength;
|
---|
141 |
|
---|
142 | /** Normalized intervals array */
|
---|
143 | protected float[] normalizedIntervals;
|
---|
144 |
|
---|
145 | /** fractions array */
|
---|
146 | protected float[] fractions;
|
---|
147 |
|
---|
148 | /** Used to determine if gradient colors are all opaque */
|
---|
149 | private int transparencyTest;
|
---|
150 |
|
---|
151 | /** Colorspace conversion lookup tables */
|
---|
152 | private static final int SRGBtoLinearRGB[] = new int[256];
|
---|
153 | private static final int LinearRGBtoSRGB[] = new int[256];
|
---|
154 |
|
---|
155 | //build the tables
|
---|
156 | static{
|
---|
157 | for (int k = 0; k < 256; k++) {
|
---|
158 | SRGBtoLinearRGB[k] = convertSRGBtoLinearRGB(k);
|
---|
159 | LinearRGBtoSRGB[k] = convertLinearRGBtoSRGB(k);
|
---|
160 | }
|
---|
161 | }
|
---|
162 |
|
---|
163 | /** Constant number of max colors between any 2 arbitrary colors.
|
---|
164 | * Used for creating and indexing gradients arrays.
|
---|
165 | */
|
---|
166 | protected static final int GRADIENT_SIZE = 256;
|
---|
167 | protected static final int GRADIENT_SIZE_INDEX = GRADIENT_SIZE -1;
|
---|
168 |
|
---|
169 | /** Maximum length of the fast single-array. If the estimated array size
|
---|
170 | * is greater than this, switch over to the slow lookup method.
|
---|
171 | * No particular reason for choosing this number, but it seems to provide
|
---|
172 | * satisfactory performance for the common case (fast lookup).
|
---|
173 | */
|
---|
174 | private static final int MAX_GRADIENT_ARRAY_SIZE = 5000;
|
---|
175 |
|
---|
176 | /** Constructor for superclass. Does some initialization, but leaves most
|
---|
177 | * of the heavy-duty math for calculateGradient(), so the subclass may do
|
---|
178 | * some other manipulation beforehand if necessary. This is not possible
|
---|
179 | * if this computation is done in the superclass constructor which always
|
---|
180 | * gets called first.
|
---|
181 | **/
|
---|
182 | public MultipleGradientPaintContext(ColorModel cm,
|
---|
183 | Rectangle deviceBounds,
|
---|
184 | Rectangle2D userBounds,
|
---|
185 | AffineTransform t,
|
---|
186 | RenderingHints hints,
|
---|
187 | float[] fractions,
|
---|
188 | Color[] colors,
|
---|
189 | MultipleGradientPaint.CycleMethodEnum
|
---|
190 | cycleMethod,
|
---|
191 | MultipleGradientPaint.ColorSpaceEnum
|
---|
192 | colorSpace)
|
---|
193 | throws NoninvertibleTransformException
|
---|
194 | {
|
---|
195 | //We have to deal with the cases where the 1st gradient stop is not
|
---|
196 | //equal to 0 and/or the last gradient stop is not equal to 1.
|
---|
197 | //In both cases, create a new point and replicate the previous
|
---|
198 | //extreme point's color.
|
---|
199 |
|
---|
200 | boolean fixFirst = false;
|
---|
201 | boolean fixLast = false;
|
---|
202 | int len = fractions.length;
|
---|
203 |
|
---|
204 | //if the first gradient stop is not equal to zero, fix this condition
|
---|
205 | if (fractions[0] != 0f) {
|
---|
206 | fixFirst = true;
|
---|
207 | len++;
|
---|
208 | }
|
---|
209 |
|
---|
210 | //if the last gradient stop is not equal to one, fix this condition
|
---|
211 | if (fractions[fractions.length - 1] != 1f) {
|
---|
212 | fixLast = true;
|
---|
213 | len++;
|
---|
214 | }
|
---|
215 |
|
---|
216 | for (int i=0; i<fractions.length-1; i++)
|
---|
217 | if (fractions[i] == fractions[i+1])
|
---|
218 | len--;
|
---|
219 |
|
---|
220 | this.fractions = new float[len];
|
---|
221 | Color [] loColors = new Color[len-1];
|
---|
222 | Color [] hiColors = new Color[len-1];
|
---|
223 | normalizedIntervals = new float[len-1];
|
---|
224 |
|
---|
225 | gradientUnderflow = colors[0].getRGB();
|
---|
226 | gradientOverflow = colors[colors.length-1].getRGB();
|
---|
227 |
|
---|
228 | int idx = 0;
|
---|
229 | if (fixFirst) {
|
---|
230 | this.fractions[0] = 0;
|
---|
231 | loColors[0] = colors[0];
|
---|
232 | hiColors[0] = colors[0];
|
---|
233 | normalizedIntervals[0] = fractions[0];
|
---|
234 | idx++;
|
---|
235 | }
|
---|
236 |
|
---|
237 | for (int i=0; i<fractions.length-1; i++) {
|
---|
238 | if (fractions[i] == fractions[i+1]) {
|
---|
239 | // System.out.println("EQ Fracts");
|
---|
240 | if (!colors[i].equals(colors[i+1])) {
|
---|
241 | hasDiscontinuity = true;
|
---|
242 | }
|
---|
243 | continue;
|
---|
244 | }
|
---|
245 | this.fractions[idx] = fractions[i];
|
---|
246 | loColors[idx] = colors[i];
|
---|
247 | hiColors[idx] = colors[i+1];
|
---|
248 | normalizedIntervals[idx] = fractions[i+1]-fractions[i];
|
---|
249 | idx++;
|
---|
250 | }
|
---|
251 |
|
---|
252 | this.fractions[idx] = fractions[fractions.length-1];
|
---|
253 |
|
---|
254 | if (fixLast) {
|
---|
255 | loColors[idx] = hiColors[idx] = colors[colors.length-1];
|
---|
256 | normalizedIntervals[idx] = 1-fractions[fractions.length-1];
|
---|
257 | idx++;
|
---|
258 | this.fractions[idx] = 1;
|
---|
259 | }
|
---|
260 |
|
---|
261 | // The inverse transform is needed to from device to user space.
|
---|
262 | // Get all the components of the inverse transform matrix.
|
---|
263 | AffineTransform tInv = t.createInverse();
|
---|
264 |
|
---|
265 | double m[] = new double[6];
|
---|
266 | tInv.getMatrix(m);
|
---|
267 | a00 = (float)m[0];
|
---|
268 | a10 = (float)m[1];
|
---|
269 | a01 = (float)m[2];
|
---|
270 | a11 = (float)m[3];
|
---|
271 | a02 = (float)m[4];
|
---|
272 | a12 = (float)m[5];
|
---|
273 |
|
---|
274 | //copy some flags
|
---|
275 | this.cycleMethod = cycleMethod;
|
---|
276 | this.colorSpace = colorSpace;
|
---|
277 |
|
---|
278 | // Setup an example Model, we may refine it later.
|
---|
279 | if (cm.getColorSpace() == lrgbmodel_A.getColorSpace())
|
---|
280 | dataModel = lrgbmodel_A;
|
---|
281 | else if (cm.getColorSpace() == srgbmodel_A.getColorSpace())
|
---|
282 | dataModel = srgbmodel_A;
|
---|
283 | else
|
---|
284 | throw new IllegalArgumentException
|
---|
285 | ("Unsupported ColorSpace for interpolation");
|
---|
286 |
|
---|
287 | calculateGradientFractions(loColors, hiColors);
|
---|
288 |
|
---|
289 | model = GraphicsUtil.coerceColorModel(dataModel,
|
---|
290 | cm.isAlphaPremultiplied());
|
---|
291 | }
|
---|
292 |
|
---|
293 |
|
---|
294 | /** This function is the meat of this class. It calculates an array of
|
---|
295 | * gradient colors based on an array of fractions and color values at those
|
---|
296 | * fractions.
|
---|
297 | */
|
---|
298 | protected final void calculateGradientFractions
|
---|
299 | (Color []loColors, Color []hiColors) {
|
---|
300 |
|
---|
301 | //if interpolation should occur in Linear RGB space, convert the
|
---|
302 | //colors using the lookup table
|
---|
303 | if (colorSpace == LinearGradientPaint.LINEAR_RGB) {
|
---|
304 | for (int i = 0; i < loColors.length; i++) {
|
---|
305 | loColors[i] =
|
---|
306 | new Color(SRGBtoLinearRGB[loColors[i].getRed()],
|
---|
307 | SRGBtoLinearRGB[loColors[i].getGreen()],
|
---|
308 | SRGBtoLinearRGB[loColors[i].getBlue()],
|
---|
309 | loColors[i].getAlpha());
|
---|
310 |
|
---|
311 | hiColors[i] =
|
---|
312 | new Color(SRGBtoLinearRGB[hiColors[i].getRed()],
|
---|
313 | SRGBtoLinearRGB[hiColors[i].getGreen()],
|
---|
314 | SRGBtoLinearRGB[hiColors[i].getBlue()],
|
---|
315 | hiColors[i].getAlpha());
|
---|
316 | }
|
---|
317 | }
|
---|
318 |
|
---|
319 | //initialize to be fully opaque for ANDing with colors
|
---|
320 | transparencyTest = 0xff000000;
|
---|
321 |
|
---|
322 | //array of interpolation arrays
|
---|
323 | gradients = new int[fractions.length - 1][];
|
---|
324 | gradientsLength = gradients.length;
|
---|
325 |
|
---|
326 | // Find smallest interval
|
---|
327 | int n = normalizedIntervals.length;
|
---|
328 |
|
---|
329 | float Imin = 1;
|
---|
330 |
|
---|
331 | for(int i = 0; i < n; i++) {
|
---|
332 | Imin = (Imin > normalizedIntervals[i]) ?
|
---|
333 | normalizedIntervals[i] : Imin;
|
---|
334 | }
|
---|
335 |
|
---|
336 | //estimate the size of the entire gradients array.
|
---|
337 | //This is to prevent a tiny interval from causing the size of array to
|
---|
338 | //explode. If the estimated size is too large, break to using
|
---|
339 | //seperate arrays for each interval, and using an indexing scheme at
|
---|
340 | //look-up time.
|
---|
341 | int estimatedSize = 0;
|
---|
342 |
|
---|
343 | if (Imin == 0) {
|
---|
344 | estimatedSize = Integer.MAX_VALUE;
|
---|
345 | hasDiscontinuity = true;
|
---|
346 | } else {
|
---|
347 | for (int i = 0; i < normalizedIntervals.length; i++) {
|
---|
348 | estimatedSize += (normalizedIntervals[i]/Imin) * GRADIENT_SIZE;
|
---|
349 | }
|
---|
350 | }
|
---|
351 |
|
---|
352 |
|
---|
353 | if (estimatedSize > MAX_GRADIENT_ARRAY_SIZE) {
|
---|
354 | //slow method
|
---|
355 | calculateMultipleArrayGradient(loColors, hiColors);
|
---|
356 | if ((cycleMethod == MultipleGradientPaint.REPEAT) &&
|
---|
357 | (gradients[0][0] !=
|
---|
358 | gradients[gradients.length-1][GRADIENT_SIZE_INDEX]))
|
---|
359 | hasDiscontinuity = true;
|
---|
360 | } else {
|
---|
361 | //fast method
|
---|
362 | calculateSingleArrayGradient(loColors, hiColors, Imin);
|
---|
363 | if ((cycleMethod == MultipleGradientPaint.REPEAT) &&
|
---|
364 | (gradient[0] != gradient[fastGradientArraySize]))
|
---|
365 | hasDiscontinuity = true;
|
---|
366 | }
|
---|
367 |
|
---|
368 | // Use the most 'economical' model (no alpha).
|
---|
369 | if((transparencyTest >>> 24) == 0xff) {
|
---|
370 | if (dataModel.getColorSpace() == lrgbmodel_NA.getColorSpace())
|
---|
371 | dataModel = lrgbmodel_NA;
|
---|
372 | else if (dataModel.getColorSpace() == srgbmodel_NA.getColorSpace())
|
---|
373 | dataModel = srgbmodel_NA;
|
---|
374 | model = dataModel;
|
---|
375 | }
|
---|
376 | }
|
---|
377 |
|
---|
378 |
|
---|
379 | /**
|
---|
380 | * FAST LOOKUP METHOD
|
---|
381 | *
|
---|
382 | * This method calculates the gradient color values and places them in a
|
---|
383 | * single int array, gradient[]. It does this by allocating space for
|
---|
384 | * each interval based on its size relative to the smallest interval in
|
---|
385 | * the array. The smallest interval is allocated 255 interpolated values
|
---|
386 | * (the maximum number of unique in-between colors in a 24 bit color
|
---|
387 | * system), and all other intervals are allocated
|
---|
388 | * size = (255 * the ratio of their size to the smallest interval).
|
---|
389 | *
|
---|
390 | * This scheme expedites a speedy retrieval because the colors are
|
---|
391 | * distributed along the array according to their user-specified
|
---|
392 | * distribution. All that is needed is a relative index from 0 to 1.
|
---|
393 | *
|
---|
394 | * The only problem with this method is that the possibility exists for
|
---|
395 | * the array size to balloon in the case where there is a
|
---|
396 | * disproportionately small gradient interval. In this case the other
|
---|
397 | * intervals will be allocated huge space, but much of that data is
|
---|
398 | * redundant. We thus need to use the space conserving scheme below.
|
---|
399 | *
|
---|
400 | * @param Imin the size of the smallest interval
|
---|
401 | *
|
---|
402 | */
|
---|
403 | private void calculateSingleArrayGradient
|
---|
404 | (Color [] loColors, Color [] hiColors, float Imin) {
|
---|
405 |
|
---|
406 | //set the flag so we know later it is a non-simple lookup
|
---|
407 | isSimpleLookup = true;
|
---|
408 |
|
---|
409 | int rgb1; //2 colors to interpolate
|
---|
410 | int rgb2;
|
---|
411 |
|
---|
412 | int gradientsTot = 1; //the eventual size of the single array
|
---|
413 |
|
---|
414 | // These are fixed point 8.16 (start with 0.5)
|
---|
415 | int aveA = 0x008000;
|
---|
416 | int aveR = 0x008000;
|
---|
417 | int aveG = 0x008000;
|
---|
418 | int aveB = 0x008000;
|
---|
419 |
|
---|
420 | //for every interval (transition between 2 colors)
|
---|
421 | for(int i=0; i < gradients.length; i++){
|
---|
422 |
|
---|
423 | //create an array whose size is based on the ratio to the
|
---|
424 | //smallest interval.
|
---|
425 | int nGradients = (int)((normalizedIntervals[i]/Imin)*255f);
|
---|
426 | gradientsTot += nGradients;
|
---|
427 | gradients[i] = new int[nGradients];
|
---|
428 |
|
---|
429 | //the the 2 colors (keyframes) to interpolate between
|
---|
430 | rgb1 = loColors[i].getRGB();
|
---|
431 | rgb2 = hiColors[i].getRGB();
|
---|
432 |
|
---|
433 | //fill this array with the colors in between rgb1 and rgb2
|
---|
434 | interpolate(rgb1, rgb2, gradients[i]);
|
---|
435 |
|
---|
436 | // Calculate Average of two colors...
|
---|
437 | int argb = gradients[i][GRADIENT_SIZE/2];
|
---|
438 | float norm = normalizedIntervals[i];
|
---|
439 | aveA += (int)(((argb>> 8)&0xFF0000)*norm);
|
---|
440 | aveR += (int)(((argb )&0xFF0000)*norm);
|
---|
441 | aveG += (int)(((argb<< 8)&0xFF0000)*norm);
|
---|
442 | aveB += (int)(((argb<<16)&0xFF0000)*norm);
|
---|
443 |
|
---|
444 | //if the colors are opaque, transparency should still be 0xff000000
|
---|
445 | transparencyTest &= rgb1;
|
---|
446 | transparencyTest &= rgb2;
|
---|
447 | }
|
---|
448 |
|
---|
449 | gradientAverage = (((aveA & 0xFF0000)<< 8) |
|
---|
450 | ((aveR & 0xFF0000) ) |
|
---|
451 | ((aveG & 0xFF0000)>> 8) |
|
---|
452 | ((aveB & 0xFF0000)>>16));
|
---|
453 |
|
---|
454 | // Put all gradients in a single array
|
---|
455 | gradient = new int[gradientsTot];
|
---|
456 | int curOffset = 0;
|
---|
457 | for(int i = 0; i < gradients.length; i++){
|
---|
458 | System.arraycopy(gradients[i], 0, gradient,
|
---|
459 | curOffset, gradients[i].length);
|
---|
460 | curOffset += gradients[i].length;
|
---|
461 | }
|
---|
462 | gradient[gradient.length-1] = hiColors[hiColors.length-1].getRGB();
|
---|
463 |
|
---|
464 | //if interpolation occurred in Linear RGB space, convert the
|
---|
465 | //gradients back to SRGB using the lookup table
|
---|
466 | if (colorSpace == LinearGradientPaint.LINEAR_RGB) {
|
---|
467 | if (dataModel.getColorSpace() ==
|
---|
468 | ColorSpace.getInstance(ColorSpace.CS_sRGB)) {
|
---|
469 | for (int i = 0; i < gradient.length; i++) {
|
---|
470 | gradient[i] =
|
---|
471 | convertEntireColorLinearRGBtoSRGB(gradient[i]);
|
---|
472 | }
|
---|
473 | gradientAverage =
|
---|
474 | convertEntireColorLinearRGBtoSRGB(gradientAverage);
|
---|
475 | }
|
---|
476 | } else {
|
---|
477 | if (dataModel.getColorSpace() ==
|
---|
478 | ColorSpace.getInstance(ColorSpace.CS_LINEAR_RGB)) {
|
---|
479 | for (int i = 0; i < gradient.length; i++) {
|
---|
480 | gradient[i] =
|
---|
481 | convertEntireColorSRGBtoLinearRGB(gradient[i]);
|
---|
482 | }
|
---|
483 | gradientAverage =
|
---|
484 | convertEntireColorSRGBtoLinearRGB(gradientAverage);
|
---|
485 | }
|
---|
486 | }
|
---|
487 |
|
---|
488 | fastGradientArraySize = gradient.length - 1;
|
---|
489 | }
|
---|
490 |
|
---|
491 |
|
---|
492 | /**
|
---|
493 | * SLOW LOOKUP METHOD
|
---|
494 | *
|
---|
495 | * This method calculates the gradient color values for each interval and
|
---|
496 | * places each into its own 255 size array. The arrays are stored in
|
---|
497 | * gradients[][]. (255 is used because this is the maximum number of
|
---|
498 | * unique colors between 2 arbitrary colors in a 24 bit color system)
|
---|
499 | *
|
---|
500 | * This method uses the minimum amount of space (only 255 * number of
|
---|
501 | * intervals), but it aggravates the lookup procedure, because now we
|
---|
502 | * have to find out which interval to select, then calculate the index
|
---|
503 | * within that interval. This causes a significant performance hit,
|
---|
504 | * because it requires this calculation be done for every point in
|
---|
505 | * the rendering loop.
|
---|
506 | *
|
---|
507 | * For those of you who are interested, this is a classic example of the
|
---|
508 | * time-space tradeoff.
|
---|
509 | *
|
---|
510 | */
|
---|
511 | private void calculateMultipleArrayGradient
|
---|
512 | (Color [] loColors, Color [] hiColors) {
|
---|
513 |
|
---|
514 | //set the flag so we know later it is a non-simple lookup
|
---|
515 | isSimpleLookup = false;
|
---|
516 |
|
---|
517 | int rgb1; //2 colors to interpolate
|
---|
518 | int rgb2;
|
---|
519 |
|
---|
520 | // These are fixed point 8.16 (start with 0.5)
|
---|
521 | int aveA = 0x008000;
|
---|
522 | int aveR = 0x008000;
|
---|
523 | int aveG = 0x008000;
|
---|
524 | int aveB = 0x008000;
|
---|
525 |
|
---|
526 | //for every interval (transition between 2 colors)
|
---|
527 | for(int i=0; i < gradients.length; i++){
|
---|
528 |
|
---|
529 | // This interval will never actually be used (zero size)
|
---|
530 | if (normalizedIntervals[i] == 0)
|
---|
531 | continue;
|
---|
532 |
|
---|
533 | //create an array of the maximum theoretical size for each interval
|
---|
534 | gradients[i] = new int[GRADIENT_SIZE];
|
---|
535 |
|
---|
536 | //get the the 2 colors
|
---|
537 | rgb1 = loColors[i].getRGB();
|
---|
538 | rgb2 = hiColors[i].getRGB();
|
---|
539 |
|
---|
540 | //fill this array with the colors in between rgb1 and rgb2
|
---|
541 | interpolate(rgb1, rgb2, gradients[i]);
|
---|
542 |
|
---|
543 | // Calculate Average of two colors...
|
---|
544 | int argb = gradients[i][GRADIENT_SIZE/2];
|
---|
545 | float norm = normalizedIntervals[i];
|
---|
546 | aveA += (int)(((argb>> 8)&0xFF0000)*norm);
|
---|
547 | aveR += (int)(((argb )&0xFF0000)*norm);
|
---|
548 | aveG += (int)(((argb<< 8)&0xFF0000)*norm);
|
---|
549 | aveB += (int)(((argb<<16)&0xFF0000)*norm);
|
---|
550 |
|
---|
551 | //if the colors are opaque, transparency should still be 0xff000000
|
---|
552 | transparencyTest &= rgb1;
|
---|
553 | transparencyTest &= rgb2;
|
---|
554 | }
|
---|
555 |
|
---|
556 | gradientAverage = (((aveA & 0xFF0000)<< 8) |
|
---|
557 | ((aveR & 0xFF0000) ) |
|
---|
558 | ((aveG & 0xFF0000)>> 8) |
|
---|
559 | ((aveB & 0xFF0000)>>16));
|
---|
560 |
|
---|
561 | //if interpolation occurred in Linear RGB space, convert the
|
---|
562 | //gradients back to SRGB using the lookup table
|
---|
563 | if (colorSpace == LinearGradientPaint.LINEAR_RGB) {
|
---|
564 | if (dataModel.getColorSpace() ==
|
---|
565 | ColorSpace.getInstance(ColorSpace.CS_sRGB)) {
|
---|
566 | for (int j = 0; j < gradients.length; j++) {
|
---|
567 | for (int i = 0; i < gradients[j].length; i++) {
|
---|
568 | gradients[j][i] =
|
---|
569 | convertEntireColorLinearRGBtoSRGB(gradients[j][i]);
|
---|
570 | }
|
---|
571 | }
|
---|
572 | gradientAverage =
|
---|
573 | convertEntireColorLinearRGBtoSRGB(gradientAverage);
|
---|
574 | }
|
---|
575 | } else {
|
---|
576 | if (dataModel.getColorSpace() ==
|
---|
577 | ColorSpace.getInstance(ColorSpace.CS_LINEAR_RGB)) {
|
---|
578 | for (int j = 0; j < gradients.length; j++) {
|
---|
579 | for (int i = 0; i < gradients[j].length; i++) {
|
---|
580 | gradients[j][i] =
|
---|
581 | convertEntireColorSRGBtoLinearRGB(gradients[j][i]);
|
---|
582 | }
|
---|
583 | }
|
---|
584 | gradientAverage =
|
---|
585 | convertEntireColorSRGBtoLinearRGB(gradientAverage);
|
---|
586 | }
|
---|
587 | }
|
---|
588 | }
|
---|
589 |
|
---|
590 | /** Yet another helper function. This one linearly interpolates between
|
---|
591 | * 2 colors, filling up the output array.
|
---|
592 | *
|
---|
593 | * @param rgb1 the start color
|
---|
594 | * @param rgb2 the end color
|
---|
595 | * @param output the output array of colors... assuming this is not null.
|
---|
596 | *
|
---|
597 | */
|
---|
598 | private void interpolate(int rgb1, int rgb2, int[] output) {
|
---|
599 |
|
---|
600 | int a1, r1, g1, b1, da, dr, dg, db; //color components
|
---|
601 |
|
---|
602 | //step between interpolated values.
|
---|
603 | float stepSize = 1/(float)output.length;
|
---|
604 |
|
---|
605 | //extract color components from packed integer
|
---|
606 | a1 = (rgb1 >> 24) & 0xff;
|
---|
607 | r1 = (rgb1 >> 16) & 0xff;
|
---|
608 | g1 = (rgb1 >> 8) & 0xff;
|
---|
609 | b1 = (rgb1 ) & 0xff;
|
---|
610 | //calculate the total change in alpha, red, green, blue
|
---|
611 | da = ((rgb2 >> 24) & 0xff) - a1;
|
---|
612 | dr = ((rgb2 >> 16) & 0xff) - r1;
|
---|
613 | dg = ((rgb2 >> 8) & 0xff) - g1;
|
---|
614 | db = ((rgb2 ) & 0xff) - b1;
|
---|
615 |
|
---|
616 | //for each step in the interval calculate the in-between color by
|
---|
617 | //multiplying the normalized current position by the total color change
|
---|
618 | //(.5 is added to prevent truncation round-off error)
|
---|
619 | for (int i = 0; i < output.length; i++) {
|
---|
620 | output[i] =
|
---|
621 | (((int) ((a1 + i * da * stepSize) + .5) << 24)) |
|
---|
622 | (((int) ((r1 + i * dr * stepSize) + .5) << 16)) |
|
---|
623 | (((int) ((g1 + i * dg * stepSize) + .5) << 8)) |
|
---|
624 | (((int) ((b1 + i * db * stepSize) + .5) ));
|
---|
625 | }
|
---|
626 | }
|
---|
627 |
|
---|
628 |
|
---|
629 | /** Yet another helper function. This one extracts the color components
|
---|
630 | * of an integer RGB triple, converts them from LinearRGB to SRGB, then
|
---|
631 | * recompacts them into an int.
|
---|
632 | */
|
---|
633 | private int convertEntireColorLinearRGBtoSRGB(int rgb) {
|
---|
634 |
|
---|
635 | int a1, r1, g1, b1; //color components
|
---|
636 |
|
---|
637 | //extract red, green, blue components
|
---|
638 | a1 = (rgb >> 24) & 0xff;
|
---|
639 | r1 = (rgb >> 16) & 0xff;
|
---|
640 | g1 = (rgb >> 8) & 0xff;
|
---|
641 | b1 = rgb & 0xff;
|
---|
642 |
|
---|
643 | //use the lookup table
|
---|
644 | r1 = LinearRGBtoSRGB[r1];
|
---|
645 | g1 = LinearRGBtoSRGB[g1];
|
---|
646 | b1 = LinearRGBtoSRGB[b1];
|
---|
647 |
|
---|
648 | //re-compact the components
|
---|
649 | return ((a1 << 24) |
|
---|
650 | (r1 << 16) |
|
---|
651 | (g1 << 8) |
|
---|
652 | b1);
|
---|
653 | }
|
---|
654 |
|
---|
655 | /** Yet another helper function. This one extracts the color components
|
---|
656 | * of an integer RGB triple, converts them from LinearRGB to SRGB, then
|
---|
657 | * recompacts them into an int.
|
---|
658 | */
|
---|
659 | private int convertEntireColorSRGBtoLinearRGB(int rgb) {
|
---|
660 |
|
---|
661 | int a1, r1, g1, b1; //color components
|
---|
662 |
|
---|
663 | //extract red, green, blue components
|
---|
664 | a1 = (rgb >> 24) & 0xff;
|
---|
665 | r1 = (rgb >> 16) & 0xff;
|
---|
666 | g1 = (rgb >> 8) & 0xff;
|
---|
667 | b1 = rgb & 0xff;
|
---|
668 |
|
---|
669 | //use the lookup table
|
---|
670 | r1 = SRGBtoLinearRGB[r1];
|
---|
671 | g1 = SRGBtoLinearRGB[g1];
|
---|
672 | b1 = SRGBtoLinearRGB[b1];
|
---|
673 |
|
---|
674 | //re-compact the components
|
---|
675 | return ((a1 << 24) |
|
---|
676 | (r1 << 16) |
|
---|
677 | (g1 << 8) |
|
---|
678 | b1);
|
---|
679 | }
|
---|
680 |
|
---|
681 |
|
---|
682 | /** Helper function to index into the gradients array. This is necessary
|
---|
683 | * because each interval has an array of colors with uniform size 255.
|
---|
684 | * However, the color intervals are not necessarily of uniform length, so
|
---|
685 | * a conversion is required.
|
---|
686 | *
|
---|
687 | * @param position the unmanipulated position. want to map this into the
|
---|
688 | * range 0 to 1
|
---|
689 | *
|
---|
690 | * @returns integer color to display
|
---|
691 | *
|
---|
692 | */
|
---|
693 | protected final int indexIntoGradientsArrays(float position) {
|
---|
694 |
|
---|
695 | //first, manipulate position value depending on the cycle method.
|
---|
696 |
|
---|
697 | if (cycleMethod == MultipleGradientPaint.NO_CYCLE) {
|
---|
698 |
|
---|
699 | if (position >= 1) { //upper bound is 1
|
---|
700 | return gradientOverflow;
|
---|
701 | }
|
---|
702 |
|
---|
703 | else if (position <= 0) { //lower bound is 0
|
---|
704 | return gradientUnderflow;
|
---|
705 | }
|
---|
706 | }
|
---|
707 |
|
---|
708 | else if (cycleMethod == MultipleGradientPaint.REPEAT) {
|
---|
709 | //get the fractional part
|
---|
710 | //(modulo behavior discards integer component)
|
---|
711 | position = position - (int)position;
|
---|
712 |
|
---|
713 | //position now be between -1 and 1
|
---|
714 |
|
---|
715 | if (position < 0) {
|
---|
716 | position = position + 1; //force it to be in the range 0-1
|
---|
717 | }
|
---|
718 |
|
---|
719 | int w=0, c1=0, c2=0;
|
---|
720 | if (isSimpleLookup) {
|
---|
721 | position *= gradient.length;
|
---|
722 | int idx1 = (int)(position);
|
---|
723 | if (idx1+1 < gradient.length)
|
---|
724 | return gradient[idx1];
|
---|
725 |
|
---|
726 | w = (int)((position-idx1)*(1<<16));
|
---|
727 | c1 = gradient[idx1];
|
---|
728 | c2 = gradient[0];
|
---|
729 | } else {
|
---|
730 | //for all the gradient interval arrays
|
---|
731 | for (int i = 0; i < gradientsLength; i++) {
|
---|
732 |
|
---|
733 | if (position < fractions[i+1]) { //this is the array we want
|
---|
734 |
|
---|
735 | float delta = position - fractions[i];
|
---|
736 |
|
---|
737 | delta = ((delta / normalizedIntervals[i]) * GRADIENT_SIZE);
|
---|
738 | //this is the interval we want.
|
---|
739 | int index = (int)delta;
|
---|
740 | if ((index+1<gradients[i].length) ||
|
---|
741 | (i+1 < gradientsLength))
|
---|
742 | return gradients[i][index];
|
---|
743 |
|
---|
744 | w = (int)((delta-index)*(1<<16));
|
---|
745 | c1 = gradients[i][index];
|
---|
746 | c2 = gradients[0][0];
|
---|
747 | break;
|
---|
748 | }
|
---|
749 | }
|
---|
750 | }
|
---|
751 |
|
---|
752 | return
|
---|
753 | (((( ( (c1>> 8) &0xFF0000)+
|
---|
754 | ((((c2>>>24) )-((c1>>>24) ))*w))&0xFF0000)<< 8) |
|
---|
755 |
|
---|
756 | ((( ( (c1 ) &0xFF0000)+
|
---|
757 | ((((c2>> 16)&0xFF)-((c1>> 16)&0xFF))*w))&0xFF0000) ) |
|
---|
758 |
|
---|
759 | ((( ( (c1<< 8) &0xFF0000)+
|
---|
760 | ((((c2>> 8)&0xFF)-((c1>> 8)&0xFF))*w))&0xFF0000)>> 8) |
|
---|
761 |
|
---|
762 | ((( ( (c1<< 16) &0xFF0000)+
|
---|
763 | ((((c2 )&0xFF)-((c1 )&0xFF))*w))&0xFF0000)>>16));
|
---|
764 |
|
---|
765 | // return c1 +
|
---|
766 | // ((( ((((c2>>>24) )-((c1>>>24) ))*w)&0xFF0000)<< 8) |
|
---|
767 | // (( ((((c2>> 16)&0xFF)-((c1>> 16)&0xFF))*w)&0xFF0000) ) |
|
---|
768 | // (( ((((c2>> 8)&0xFF)-((c1>> 8)&0xFF))*w)&0xFF0000)>> 8) |
|
---|
769 | // (( ((((c2 )&0xFF)-((c1 )&0xFF))*w)&0xFF0000)>>16));
|
---|
770 | }
|
---|
771 |
|
---|
772 | else { //cycleMethod == MultipleGradientPaint.REFLECT
|
---|
773 |
|
---|
774 | if (position < 0) {
|
---|
775 | position = -position; //take absolute value
|
---|
776 | }
|
---|
777 |
|
---|
778 | int part = (int)position; //take the integer part
|
---|
779 |
|
---|
780 | position = position - part; //get the fractional part
|
---|
781 |
|
---|
782 | if ((part & 0x00000001) == 1) { //if integer part is odd
|
---|
783 | position = 1 - position; //want the reflected color instead
|
---|
784 | }
|
---|
785 | }
|
---|
786 |
|
---|
787 | //now, get the color based on this 0-1 position:
|
---|
788 |
|
---|
789 | if (isSimpleLookup) { //easy to compute: just scale index by array size
|
---|
790 | return gradient[(int)(position * fastGradientArraySize)];
|
---|
791 | }
|
---|
792 |
|
---|
793 | else { //more complicated computation, to save space
|
---|
794 |
|
---|
795 | //for all the gradient interval arrays
|
---|
796 | for (int i = 0; i < gradientsLength; i++) {
|
---|
797 |
|
---|
798 | if (position < fractions[i+1]) { //this is the array we want
|
---|
799 |
|
---|
800 | float delta = position - fractions[i];
|
---|
801 |
|
---|
802 | //this is the interval we want.
|
---|
803 | int index = (int)((delta / normalizedIntervals[i])
|
---|
804 | * (GRADIENT_SIZE_INDEX));
|
---|
805 |
|
---|
806 | return gradients[i][index];
|
---|
807 | }
|
---|
808 | }
|
---|
809 |
|
---|
810 | }
|
---|
811 |
|
---|
812 | return gradientOverflow;
|
---|
813 | }
|
---|
814 |
|
---|
815 |
|
---|
816 | /** Helper function to index into the gradients array. This is necessary
|
---|
817 | * because each interval has an array of colors with uniform size 255.
|
---|
818 | * However, the color intervals are not necessarily of uniform length, so
|
---|
819 | * a conversion is required. This version also does anti-aliasing by
|
---|
820 | * averaging the gradient over position+/-(sz/2).
|
---|
821 | *
|
---|
822 | * @param position the unmanipulated position. want to map this into the
|
---|
823 | * range 0 to 1
|
---|
824 | * @param sz the size in gradient space to average.
|
---|
825 | *
|
---|
826 | * @returns ARGB integer color to display
|
---|
827 | */
|
---|
828 | protected final int indexGradientAntiAlias(float position, float sz) {
|
---|
829 | //first, manipulate position value depending on the cycle method.
|
---|
830 | if (cycleMethod == MultipleGradientPaint.NO_CYCLE) {
|
---|
831 | if (DEBUG) System.out.println("NO_CYCLE");
|
---|
832 | float p1 = position-(sz/2);
|
---|
833 | float p2 = position+(sz/2);
|
---|
834 |
|
---|
835 | if (p1 >= 1)
|
---|
836 | return gradientOverflow;
|
---|
837 |
|
---|
838 | if (p2 <= 0)
|
---|
839 | return gradientUnderflow;
|
---|
840 |
|
---|
841 | int interior;
|
---|
842 | float top_weight=0, bottom_weight=0, frac;
|
---|
843 | if (p2 >= 1) {
|
---|
844 | top_weight = (p2-1)/sz;
|
---|
845 | if (p1 <= 0) {
|
---|
846 | bottom_weight = -p1/sz;
|
---|
847 | frac=1;
|
---|
848 | interior = gradientAverage;
|
---|
849 | } else {
|
---|
850 | frac=1-p1;
|
---|
851 | interior = getAntiAlias(p1, true, 1, false, 1-p1, 1);
|
---|
852 | }
|
---|
853 | } else if (p1 <= 0) {
|
---|
854 | bottom_weight = -p1/sz;
|
---|
855 | frac = p2;
|
---|
856 | interior = getAntiAlias(0, true, p2, false, p2, 1);
|
---|
857 | } else
|
---|
858 | return getAntiAlias(p1, true, p2, false, sz, 1);
|
---|
859 |
|
---|
860 | int norm = (int)((1<<16)*frac/sz);
|
---|
861 | int pA = (((interior>>>20)&0xFF0)*norm)>>16;
|
---|
862 | int pR = (((interior>> 12)&0xFF0)*norm)>>16;
|
---|
863 | int pG = (((interior>> 4)&0xFF0)*norm)>>16;
|
---|
864 | int pB = (((interior<< 4)&0xFF0)*norm)>>16;
|
---|
865 |
|
---|
866 | if (bottom_weight != 0) {
|
---|
867 | int bPix = gradientUnderflow;
|
---|
868 | // System.out.println("ave: " + gradientAverage);
|
---|
869 | norm = (int)((1<<16)*bottom_weight);
|
---|
870 | pA += (((bPix>>>20) & 0xFF0)*norm)>>16;
|
---|
871 | pR += (((bPix>> 12) & 0xFF0)*norm)>>16;
|
---|
872 | pG += (((bPix>> 4) & 0xFF0)*norm)>>16;
|
---|
873 | pB += (((bPix<< 4) & 0xFF0)*norm)>>16;
|
---|
874 | }
|
---|
875 |
|
---|
876 | if (top_weight != 0) {
|
---|
877 | int tPix = gradientOverflow;
|
---|
878 |
|
---|
879 | norm = (int)((1<<16)*top_weight);
|
---|
880 | pA += (((tPix>>>20) & 0xFF0)*norm)>>16;
|
---|
881 | pR += (((tPix>> 12) & 0xFF0)*norm)>>16;
|
---|
882 | pG += (((tPix>> 4) & 0xFF0)*norm)>>16;
|
---|
883 | pB += (((tPix<< 4) & 0xFF0)*norm)>>16;
|
---|
884 | }
|
---|
885 |
|
---|
886 | return (((pA&0xFF0)<<20) |
|
---|
887 | ((pR&0xFF0)<<12) |
|
---|
888 | ((pG&0xFF0)<< 4) |
|
---|
889 | ((pB&0xFF0)>> 4));
|
---|
890 | }
|
---|
891 |
|
---|
892 | // See how many times we are going to "wrap around" the gradient,
|
---|
893 | // array.
|
---|
894 | int intSz = (int)sz;
|
---|
895 |
|
---|
896 | float weight = 1f;
|
---|
897 | if (intSz != 0) {
|
---|
898 | // We need to make sure that sz is < 1.0 otherwise
|
---|
899 | // p1 and p2 my pass each other which will cause no end of
|
---|
900 | // trouble.
|
---|
901 | sz -= intSz;
|
---|
902 | weight = sz/(intSz+sz);
|
---|
903 | if (weight < 0.1)
|
---|
904 | // The part of the color from the location will be swamped
|
---|
905 | // by the averaged part of the gradient so just use the
|
---|
906 | // average color for the gradient.
|
---|
907 | return gradientAverage;
|
---|
908 | }
|
---|
909 |
|
---|
910 | // So close to full gradient just use the average value...
|
---|
911 | if (sz > 0.99)
|
---|
912 | return gradientAverage;
|
---|
913 |
|
---|
914 | // Go up and down from position by 1/2 sz.
|
---|
915 | float p1 = position-(sz/2);
|
---|
916 | float p2 = position+(sz/2);
|
---|
917 | if (DEBUG) System.out.println("P1: " + p1 + " P2: " + p2);
|
---|
918 |
|
---|
919 | // These indicate the direction to go from p1 and p2 when
|
---|
920 | // averaging...
|
---|
921 | boolean p1_up=true;
|
---|
922 | boolean p2_up=false;
|
---|
923 |
|
---|
924 | if (cycleMethod == MultipleGradientPaint.REPEAT) {
|
---|
925 | if (DEBUG) System.out.println("REPEAT");
|
---|
926 |
|
---|
927 | // Get positions between -1 and 1
|
---|
928 | p1=p1-(int)p1;
|
---|
929 | p2=p2-(int)p2;
|
---|
930 |
|
---|
931 | // force to be in rage 0-1.
|
---|
932 | if (p1 <0) p1 += 1;
|
---|
933 | if (p2 <0) p2 += 1;
|
---|
934 | }
|
---|
935 |
|
---|
936 | else { //cycleMethod == MultipleGradientPaint.REFLECT
|
---|
937 | if (DEBUG) System.out.println("REFLECT");
|
---|
938 |
|
---|
939 | //take absolute values
|
---|
940 | // Note when we reflect we change sense of p1/2_up.
|
---|
941 | if (p2 < 0) {
|
---|
942 | p1 = -p1; p1_up = !p1_up;
|
---|
943 | p2 = -p2; p2_up = !p2_up;
|
---|
944 | } else if (p1 < 0) {
|
---|
945 | p1 = -p1; p1_up = !p1_up;
|
---|
946 | }
|
---|
947 |
|
---|
948 | int part1, part2;
|
---|
949 | part1 = (int)p1; // take the integer part
|
---|
950 | p1 = p1 - part1; // get the fractional part
|
---|
951 |
|
---|
952 | part2 = (int)p2; // take the integer part
|
---|
953 | p2 = p2 - part2; // get the fractional part
|
---|
954 |
|
---|
955 | // if integer part is odd we want the reflected color instead.
|
---|
956 | // Note when we reflect we change sense of p1/2_up.
|
---|
957 | if ((part1 & 0x01) == 1) {
|
---|
958 | p1 = 1-p1;
|
---|
959 | p1_up = !p1_up;
|
---|
960 | }
|
---|
961 |
|
---|
962 | if ((part2 & 0x01) == 1) {
|
---|
963 | p2 = 1-p2;
|
---|
964 | p2_up = !p2_up;
|
---|
965 | }
|
---|
966 |
|
---|
967 | // Check if in the end they just got switched around.
|
---|
968 | // this commonly happens if they both end up negative.
|
---|
969 | if ((p1 > p2) && !p1_up && p2_up) {
|
---|
970 | float t = p1;
|
---|
971 | p1 = p2;
|
---|
972 | p2 = t;
|
---|
973 | p1_up = true;
|
---|
974 | p2_up = false;
|
---|
975 | }
|
---|
976 | }
|
---|
977 |
|
---|
978 | return getAntiAlias(p1, p1_up, p2, p2_up, sz, weight);
|
---|
979 | }
|
---|
980 |
|
---|
981 |
|
---|
982 | private final int getAntiAlias(float p1, boolean p1_up,
|
---|
983 | float p2, boolean p2_up,
|
---|
984 | float sz, float weight) {
|
---|
985 |
|
---|
986 | // Until the last set of ops these are 28.4 fixed point values.
|
---|
987 | int ach=0, rch=0, gch=0, bch=0;
|
---|
988 | if (isSimpleLookup) {
|
---|
989 | p1 *= fastGradientArraySize;
|
---|
990 | p2 *= fastGradientArraySize;
|
---|
991 |
|
---|
992 | int idx1 = (int)p1;
|
---|
993 | int idx2 = (int)p2;
|
---|
994 |
|
---|
995 | int i, pix;
|
---|
996 |
|
---|
997 | if (p1_up && !p2_up && (idx1 <= idx2)) {
|
---|
998 |
|
---|
999 | if (idx1 == idx2)
|
---|
1000 | return gradient[idx1];
|
---|
1001 |
|
---|
1002 | // Sum between idx1 and idx2.
|
---|
1003 | for (i=idx1+1; i<idx2; i++) {
|
---|
1004 | pix = gradient[i];
|
---|
1005 | ach += ((pix>>>20)&0xFF0);
|
---|
1006 | rch += ((pix>>>12)&0xFF0);
|
---|
1007 | gch += ((pix>>> 4)&0xFF0);
|
---|
1008 | bch += ((pix<< 4)&0xFF0);
|
---|
1009 | }
|
---|
1010 | } else {
|
---|
1011 | // Do the bulk of the work, all the whole gradient entries
|
---|
1012 | // for idx1 and idx2.
|
---|
1013 | if (p1_up) {
|
---|
1014 | for (i=idx1+1; i<fastGradientArraySize; i++) {
|
---|
1015 | pix = gradient[i];
|
---|
1016 | ach += ((pix>>>20)&0xFF0);
|
---|
1017 | rch += ((pix>>>12)&0xFF0);
|
---|
1018 | gch += ((pix>>> 4)&0xFF0);
|
---|
1019 | bch += ((pix<< 4)&0xFF0);
|
---|
1020 | }
|
---|
1021 | } else {
|
---|
1022 | for (i=0; i<idx1; i++) {
|
---|
1023 | pix = gradient[i];
|
---|
1024 | ach += ((pix>>>20)&0xFF0);
|
---|
1025 | rch += ((pix>>>12)&0xFF0);
|
---|
1026 | gch += ((pix>>> 4)&0xFF0);
|
---|
1027 | bch += ((pix<< 4)&0xFF0);
|
---|
1028 | }
|
---|
1029 | }
|
---|
1030 |
|
---|
1031 | if (p2_up) {
|
---|
1032 | for (i=idx2+1; i<fastGradientArraySize; i++) {
|
---|
1033 | pix = gradient[i];
|
---|
1034 | ach += ((pix>>>20)&0xFF0);
|
---|
1035 | rch += ((pix>>>12)&0xFF0);
|
---|
1036 | gch += ((pix>>> 4)&0xFF0);
|
---|
1037 | bch += ((pix<< 4)&0xFF0);
|
---|
1038 | }
|
---|
1039 | } else {
|
---|
1040 | for (i=0; i<idx2; i++) {
|
---|
1041 | pix = gradient[i];
|
---|
1042 | ach += ((pix>>>20)&0xFF0);
|
---|
1043 | rch += ((pix>>>12)&0xFF0);
|
---|
1044 | gch += ((pix>>> 4)&0xFF0);
|
---|
1045 | bch += ((pix<< 4)&0xFF0);
|
---|
1046 | }
|
---|
1047 | }
|
---|
1048 | }
|
---|
1049 |
|
---|
1050 | int norm, isz;
|
---|
1051 |
|
---|
1052 | // Normalize the summation so far...
|
---|
1053 | isz = (int)((1<<16)/(sz*fastGradientArraySize));
|
---|
1054 | ach = (ach*isz)>>16;
|
---|
1055 | rch = (rch*isz)>>16;
|
---|
1056 | gch = (gch*isz)>>16;
|
---|
1057 | bch = (bch*isz)>>16;
|
---|
1058 |
|
---|
1059 | // Clean up with the partial buckets at each end.
|
---|
1060 | if (p1_up) norm = (int)((1-(p1-idx1))*isz);
|
---|
1061 | else norm = (int)( (p1-idx1) *isz);
|
---|
1062 | pix = gradient[idx1];
|
---|
1063 | ach += (((pix>>>20)&0xFF0) *norm)>>16;
|
---|
1064 | rch += (((pix>>>12)&0xFF0) *norm)>>16;
|
---|
1065 | gch += (((pix>>> 4)&0xFF0) *norm)>>16;
|
---|
1066 | bch += (((pix<< 4)&0xFF0) *norm)>>16;
|
---|
1067 |
|
---|
1068 | if (p2_up) norm = (int)((1-(p2-idx2))*isz);
|
---|
1069 | else norm = (int)( (p2-idx2) *isz);
|
---|
1070 | pix = gradient[idx2];
|
---|
1071 | ach += (((pix>>>20)&0xFF0) *norm)>>16;
|
---|
1072 | rch += (((pix>>>12)&0xFF0) *norm)>>16;
|
---|
1073 | gch += (((pix>>> 4)&0xFF0) *norm)>>16;
|
---|
1074 | bch += (((pix<< 4)&0xFF0) *norm)>>16;
|
---|
1075 |
|
---|
1076 | // Round and drop the 4bits frac.
|
---|
1077 | ach = (ach+0x08)>>4;
|
---|
1078 | rch = (rch+0x08)>>4;
|
---|
1079 | gch = (gch+0x08)>>4;
|
---|
1080 | bch = (bch+0x08)>>4;
|
---|
1081 |
|
---|
1082 | } else {
|
---|
1083 | int idx1=0, idx2=0;
|
---|
1084 | int i1=-1, i2=-1;
|
---|
1085 | float f1=0, f2=0;
|
---|
1086 | // Find which gradient interval our points fall into.
|
---|
1087 | for (int i = 0; i < gradientsLength; i++) {
|
---|
1088 | if ((p1 < fractions[i+1]) && (i1 == -1)) {
|
---|
1089 | //this is the array we want
|
---|
1090 | i1 = i;
|
---|
1091 | f1 = p1 - fractions[i];
|
---|
1092 |
|
---|
1093 | f1 = ((f1/normalizedIntervals[i])
|
---|
1094 | *GRADIENT_SIZE_INDEX);
|
---|
1095 | //this is the interval we want.
|
---|
1096 | idx1 = (int)f1;
|
---|
1097 | if (i2 != -1) break;
|
---|
1098 | }
|
---|
1099 | if ((p2 < fractions[i+1]) && (i2 == -1)) {
|
---|
1100 | //this is the array we want
|
---|
1101 | i2 = i;
|
---|
1102 | f2 = p2 - fractions[i];
|
---|
1103 |
|
---|
1104 | f2 = ((f2/normalizedIntervals[i])
|
---|
1105 | *GRADIENT_SIZE_INDEX);
|
---|
1106 | //this is the interval we want.
|
---|
1107 | idx2 = (int)f2;
|
---|
1108 | if (i1 != -1) break;
|
---|
1109 | }
|
---|
1110 | }
|
---|
1111 |
|
---|
1112 | if (i1 == -1) {
|
---|
1113 | i1 = gradients.length - 1;
|
---|
1114 | f1 = idx1 = GRADIENT_SIZE_INDEX;
|
---|
1115 | }
|
---|
1116 |
|
---|
1117 | if (i2 == -1) {
|
---|
1118 | i2 = gradients.length - 1;
|
---|
1119 | f2 = idx2 = GRADIENT_SIZE_INDEX;
|
---|
1120 | }
|
---|
1121 |
|
---|
1122 | if (DEBUG) System.out.println("I1: " + i1 + " Idx1: " + idx1 +
|
---|
1123 | " I2: " + i2 + " Idx2: " + idx2);
|
---|
1124 |
|
---|
1125 | // Simple case within one gradient array (so the average
|
---|
1126 | // of the two idx gives us the true average of colors).
|
---|
1127 | if ((i1 == i2) && (idx1 <= idx2) && p1_up && !p2_up)
|
---|
1128 | return gradients[i1][(idx1+idx2+1)>>1];
|
---|
1129 |
|
---|
1130 | // i1 != i2
|
---|
1131 |
|
---|
1132 | int pix, norm;
|
---|
1133 | int base = (int)((1<<16)/sz);
|
---|
1134 | if ((i1 < i2) && p1_up && !p2_up) {
|
---|
1135 | norm = (int)((base
|
---|
1136 | *normalizedIntervals[i1]
|
---|
1137 | *(GRADIENT_SIZE_INDEX-f1))
|
---|
1138 | /GRADIENT_SIZE_INDEX);
|
---|
1139 | pix = gradients[i1][(idx1+GRADIENT_SIZE)>>1];
|
---|
1140 | ach += (((pix>>>20)&0xFF0) *norm)>>16;
|
---|
1141 | rch += (((pix>>>12)&0xFF0) *norm)>>16;
|
---|
1142 | gch += (((pix>>> 4)&0xFF0) *norm)>>16;
|
---|
1143 | bch += (((pix<< 4)&0xFF0) *norm)>>16;
|
---|
1144 |
|
---|
1145 | for (int i=i1+1; i<i2; i++) {
|
---|
1146 | norm = (int)(base*normalizedIntervals[i]);
|
---|
1147 | pix = gradients[i][GRADIENT_SIZE>>1];
|
---|
1148 |
|
---|
1149 | ach += (((pix>>>20)&0xFF0) *norm)>>16;
|
---|
1150 | rch += (((pix>>>12)&0xFF0) *norm)>>16;
|
---|
1151 | gch += (((pix>>> 4)&0xFF0) *norm)>>16;
|
---|
1152 | bch += (((pix<< 4)&0xFF0) *norm)>>16;
|
---|
1153 | }
|
---|
1154 |
|
---|
1155 | norm = (int)((base*normalizedIntervals[i2]*f2)
|
---|
1156 | /GRADIENT_SIZE_INDEX);
|
---|
1157 | pix = gradients[i2][(idx2+1)>>1];
|
---|
1158 | ach += (((pix>>>20)&0xFF0) *norm)>>16;
|
---|
1159 | rch += (((pix>>>12)&0xFF0) *norm)>>16;
|
---|
1160 | gch += (((pix>>> 4)&0xFF0) *norm)>>16;
|
---|
1161 | bch += (((pix<< 4)&0xFF0) *norm)>>16;
|
---|
1162 | } else {
|
---|
1163 | if (p1_up) {
|
---|
1164 | norm = (int)((base
|
---|
1165 | *normalizedIntervals[i1]
|
---|
1166 | *(GRADIENT_SIZE_INDEX-f1))
|
---|
1167 | /GRADIENT_SIZE_INDEX);
|
---|
1168 | pix = gradients[i1][(idx1+GRADIENT_SIZE)>>1];
|
---|
1169 | } else {
|
---|
1170 | norm = (int)((base*normalizedIntervals[i1]*f1)
|
---|
1171 | /GRADIENT_SIZE_INDEX);
|
---|
1172 | pix = gradients[i1][(idx1+1)>>1];
|
---|
1173 | }
|
---|
1174 | ach += (((pix>>>20)&0xFF0) *norm)>>16;
|
---|
1175 | rch += (((pix>>>12)&0xFF0) *norm)>>16;
|
---|
1176 | gch += (((pix>>> 4)&0xFF0) *norm)>>16;
|
---|
1177 | bch += (((pix<< 4)&0xFF0) *norm)>>16;
|
---|
1178 |
|
---|
1179 | if (p2_up) {
|
---|
1180 | norm = (int)((base
|
---|
1181 | *normalizedIntervals[i2]
|
---|
1182 | *(GRADIENT_SIZE_INDEX-f2))
|
---|
1183 | /GRADIENT_SIZE_INDEX);
|
---|
1184 | pix = gradients[i2][(idx2+GRADIENT_SIZE)>>1];
|
---|
1185 | } else {
|
---|
1186 | norm = (int)((base*normalizedIntervals[i2]*f2)
|
---|
1187 | /GRADIENT_SIZE_INDEX);
|
---|
1188 | pix = gradients[i2][(idx2+1)>>1];
|
---|
1189 | }
|
---|
1190 | ach += (((pix>>>20)&0xFF0) *norm)>>16;
|
---|
1191 | rch += (((pix>>>12)&0xFF0) *norm)>>16;
|
---|
1192 | gch += (((pix>>> 4)&0xFF0) *norm)>>16;
|
---|
1193 | bch += (((pix<< 4)&0xFF0) *norm)>>16;
|
---|
1194 |
|
---|
1195 | if (p1_up) {
|
---|
1196 | for (int i=i1+1; i<gradientsLength; i++) {
|
---|
1197 | norm = (int)(base*normalizedIntervals[i]);
|
---|
1198 | pix = gradients[i][GRADIENT_SIZE>>1];
|
---|
1199 |
|
---|
1200 | ach += (((pix>>>20)&0xFF0) *norm)>>16;
|
---|
1201 | rch += (((pix>>>12)&0xFF0) *norm)>>16;
|
---|
1202 | gch += (((pix>>> 4)&0xFF0) *norm)>>16;
|
---|
1203 | bch += (((pix<< 4)&0xFF0) *norm)>>16;
|
---|
1204 | }
|
---|
1205 | } else {
|
---|
1206 | for (int i=0; i<i1; i++) {
|
---|
1207 | norm = (int)(base*normalizedIntervals[i]);
|
---|
1208 | pix = gradients[i][GRADIENT_SIZE>>1];
|
---|
1209 |
|
---|
1210 | ach += (((pix>>>20)&0xFF0) *norm)>>16;
|
---|
1211 | rch += (((pix>>>12)&0xFF0) *norm)>>16;
|
---|
1212 | gch += (((pix>>> 4)&0xFF0) *norm)>>16;
|
---|
1213 | bch += (((pix<< 4)&0xFF0) *norm)>>16;
|
---|
1214 | }
|
---|
1215 | }
|
---|
1216 |
|
---|
1217 | if (p2_up) {
|
---|
1218 | for (int i=i2+1; i<gradientsLength; i++) {
|
---|
1219 | norm = (int)(base*normalizedIntervals[i]);
|
---|
1220 | pix = gradients[i][GRADIENT_SIZE>>1];
|
---|
1221 |
|
---|
1222 | ach += (((pix>>>20)&0xFF0) *norm)>>16;
|
---|
1223 | rch += (((pix>>>12)&0xFF0) *norm)>>16;
|
---|
1224 | gch += (((pix>>> 4)&0xFF0) *norm)>>16;
|
---|
1225 | bch += (((pix<< 4)&0xFF0) *norm)>>16;
|
---|
1226 | }
|
---|
1227 | } else {
|
---|
1228 | for (int i=0; i<i2; i++) {
|
---|
1229 | norm = (int)(base*normalizedIntervals[i]);
|
---|
1230 | pix = gradients[i][GRADIENT_SIZE>>1];
|
---|
1231 |
|
---|
1232 | ach += (((pix>>>20)&0xFF0) *norm)>>16;
|
---|
1233 | rch += (((pix>>>12)&0xFF0) *norm)>>16;
|
---|
1234 | gch += (((pix>>> 4)&0xFF0) *norm)>>16;
|
---|
1235 | bch += (((pix<< 4)&0xFF0) *norm)>>16;
|
---|
1236 | }
|
---|
1237 | }
|
---|
1238 |
|
---|
1239 | }
|
---|
1240 | ach = (ach+0x08)>>4;
|
---|
1241 | rch = (rch+0x08)>>4;
|
---|
1242 | gch = (gch+0x08)>>4;
|
---|
1243 | bch = (bch+0x08)>>4;
|
---|
1244 | if (DEBUG) System.out.println("Pix: [" + ach + ", " + rch +
|
---|
1245 | ", " + gch + ", " + bch + "]");
|
---|
1246 | }
|
---|
1247 |
|
---|
1248 | if (weight != 1) {
|
---|
1249 | // System.out.println("ave: " + gradientAverage);
|
---|
1250 | int aveW = (int)((1<<16)*(1-weight));
|
---|
1251 | int aveA = ((gradientAverage>>>24) & 0xFF)*aveW;
|
---|
1252 | int aveR = ((gradientAverage>> 16) & 0xFF)*aveW;
|
---|
1253 | int aveG = ((gradientAverage>> 8) & 0xFF)*aveW;
|
---|
1254 | int aveB = ((gradientAverage ) & 0xFF)*aveW;
|
---|
1255 |
|
---|
1256 | int iw = (int)(weight*(1<<16));
|
---|
1257 | ach = ((ach*iw)+aveA)>>16;
|
---|
1258 | rch = ((rch*iw)+aveR)>>16;
|
---|
1259 | gch = ((gch*iw)+aveG)>>16;
|
---|
1260 | bch = ((bch*iw)+aveB)>>16;
|
---|
1261 | }
|
---|
1262 |
|
---|
1263 | return ((ach<<24) | (rch<<16) | (gch<<8) | bch);
|
---|
1264 | }
|
---|
1265 |
|
---|
1266 |
|
---|
1267 | /** Helper function to convert a color component in sRGB space to linear
|
---|
1268 | * RGB space. Used to build a static lookup table.
|
---|
1269 | */
|
---|
1270 | private static int convertSRGBtoLinearRGB(int color) {
|
---|
1271 |
|
---|
1272 | float input, output;
|
---|
1273 |
|
---|
1274 | input = ((float) color) / 255.0f;
|
---|
1275 | if (input <= 0.04045f) {
|
---|
1276 | output = input / 12.92f;
|
---|
1277 | }
|
---|
1278 | else {
|
---|
1279 | output = (float) Math.pow((input + 0.055) / 1.055, 2.4);
|
---|
1280 | }
|
---|
1281 | int o = Math.round(output * 255.0f);
|
---|
1282 |
|
---|
1283 | return o;
|
---|
1284 | }
|
---|
1285 |
|
---|
1286 | /** Helper function to convert a color component in linear RGB space to
|
---|
1287 | * SRGB space. Used to build a static lookup table.
|
---|
1288 | */
|
---|
1289 | private static int convertLinearRGBtoSRGB(int color) {
|
---|
1290 |
|
---|
1291 | float input, output;
|
---|
1292 |
|
---|
1293 | input = ((float) color) / 255.0f;
|
---|
1294 |
|
---|
1295 | if (input <= 0.0031308) {
|
---|
1296 | output = input * 12.92f;
|
---|
1297 | }
|
---|
1298 | else {
|
---|
1299 | output = (1.055f *
|
---|
1300 | ((float) Math.pow(input, (1.0 / 2.4)))) - 0.055f;
|
---|
1301 | }
|
---|
1302 |
|
---|
1303 | int o = Math.round(output * 255.0f);
|
---|
1304 |
|
---|
1305 | return o;
|
---|
1306 | }
|
---|
1307 |
|
---|
1308 |
|
---|
1309 | /** Superclass getRaster... */
|
---|
1310 | public final Raster getRaster(int x, int y, int w, int h) {
|
---|
1311 | if (w == 0 || h == 0) {
|
---|
1312 | return null;
|
---|
1313 | }
|
---|
1314 |
|
---|
1315 | //
|
---|
1316 | // If working raster is big enough, reuse it. Otherwise,
|
---|
1317 | // build a large enough new one.
|
---|
1318 | //
|
---|
1319 | WritableRaster raster = saved;
|
---|
1320 | if (raster == null || raster.getWidth() < w || raster.getHeight() < h)
|
---|
1321 | {
|
---|
1322 | raster = getCachedRaster(dataModel, w, h);
|
---|
1323 | saved = raster;
|
---|
1324 | }
|
---|
1325 |
|
---|
1326 | // Access raster internal int array. Because we use a DirectColorModel,
|
---|
1327 | // we know the DataBuffer is of type DataBufferInt and the SampleModel
|
---|
1328 | // is SinglePixelPackedSampleModel.
|
---|
1329 | // Adjust for initial offset in DataBuffer and also for the scanline
|
---|
1330 | // stride.
|
---|
1331 | //
|
---|
1332 | DataBufferInt rasterDB = (DataBufferInt)raster.getDataBuffer();
|
---|
1333 | int[] pixels = rasterDB.getBankData()[0];
|
---|
1334 | int off = rasterDB.getOffset();
|
---|
1335 | int scanlineStride = ((SinglePixelPackedSampleModel)
|
---|
1336 | raster.getSampleModel()).getScanlineStride();
|
---|
1337 | int adjust = scanlineStride - w;
|
---|
1338 |
|
---|
1339 | fillRaster(pixels, off, adjust, x, y, w, h); //delegate to subclass.
|
---|
1340 |
|
---|
1341 | GraphicsUtil.coerceData(raster, dataModel,
|
---|
1342 | model.isAlphaPremultiplied());
|
---|
1343 |
|
---|
1344 |
|
---|
1345 | return raster;
|
---|
1346 | }
|
---|
1347 |
|
---|
1348 | /** Subclasses should implement this. */
|
---|
1349 | protected abstract void fillRaster(int pixels[], int off, int adjust,
|
---|
1350 | int x, int y, int w, int h);
|
---|
1351 |
|
---|
1352 |
|
---|
1353 | /** Took this cacheRaster code from GradientPaint. It appears to recycle
|
---|
1354 | * rasters for use by any other instance, as long as they are sufficiently
|
---|
1355 | * large.
|
---|
1356 | */
|
---|
1357 | protected final
|
---|
1358 | static synchronized WritableRaster getCachedRaster
|
---|
1359 | (ColorModel cm, int w, int h) {
|
---|
1360 | if (cm == cachedModel) {
|
---|
1361 | if (cached != null) {
|
---|
1362 | WritableRaster ras = (WritableRaster) cached.get();
|
---|
1363 | if (ras != null &&
|
---|
1364 | ras.getWidth() >= w &&
|
---|
1365 | ras.getHeight() >= h)
|
---|
1366 | {
|
---|
1367 | cached = null;
|
---|
1368 | return ras;
|
---|
1369 | }
|
---|
1370 | }
|
---|
1371 | }
|
---|
1372 | // Don't create rediculously small rasters...
|
---|
1373 | if (w<32) w=32;
|
---|
1374 | if (h<32) h=32;
|
---|
1375 | return cm.createCompatibleWritableRaster(w, h);
|
---|
1376 | }
|
---|
1377 |
|
---|
1378 | /** Took this cacheRaster code from GradientPaint. It appears to recycle
|
---|
1379 | * rasters for use by any other instance, as long as they are sufficiently
|
---|
1380 | * large.
|
---|
1381 | */
|
---|
1382 | protected final
|
---|
1383 | static synchronized void putCachedRaster(ColorModel cm,
|
---|
1384 | WritableRaster ras) {
|
---|
1385 | if (cached != null) {
|
---|
1386 | WritableRaster cras = (WritableRaster) cached.get();
|
---|
1387 | if (cras != null) {
|
---|
1388 | int cw = cras.getWidth();
|
---|
1389 | int ch = cras.getHeight();
|
---|
1390 | int iw = ras.getWidth();
|
---|
1391 | int ih = ras.getHeight();
|
---|
1392 | if (cw >= iw && ch >= ih) {
|
---|
1393 | return;
|
---|
1394 | }
|
---|
1395 | if (cw * ch >= iw * ih) {
|
---|
1396 | return;
|
---|
1397 | }
|
---|
1398 | }
|
---|
1399 | }
|
---|
1400 | cachedModel = cm;
|
---|
1401 | cached = new WeakReference(ras);
|
---|
1402 | }
|
---|
1403 |
|
---|
1404 | /**
|
---|
1405 | * Release the resources allocated for the operation.
|
---|
1406 | */
|
---|
1407 | public final void dispose() {
|
---|
1408 | if (saved != null) {
|
---|
1409 | putCachedRaster(model, saved);
|
---|
1410 | saved = null;
|
---|
1411 | }
|
---|
1412 | }
|
---|
1413 |
|
---|
1414 | /**
|
---|
1415 | * Return the ColorModel of the output.
|
---|
1416 | */
|
---|
1417 | public final ColorModel getColorModel() {
|
---|
1418 | return model;
|
---|
1419 | }
|
---|
1420 | }
|
---|
1421 |
|
---|