001: /*
002: * $RCSfile: ColorConvertOpImage.java,v $
003: *
004: * Copyright (c) 2005 Sun Microsystems, Inc. All rights reserved.
005: *
006: * Use is subject to license terms.
007: *
008: * $Revision: 1.4 $
009: * $Date: 2006/06/16 20:25:49 $
010: * $State: Exp $
011: */
012: package com.sun.media.jai.opimage;
013:
014: import java.awt.Point;
015: import java.awt.Rectangle;
016: import java.awt.RenderingHints;
017: import java.awt.Transparency;
018: import java.awt.color.ColorSpace;
019: import java.awt.image.ColorConvertOp;
020: import java.awt.image.ColorModel;
021: import java.awt.image.ComponentColorModel;
022: import java.awt.image.DataBuffer;
023: import java.awt.image.Raster;
024: import java.awt.image.RenderedImage;
025: import java.awt.image.SampleModel;
026: import java.awt.image.WritableRaster;
027: import java.text.NumberFormat;
028: import java.util.ArrayList;
029: import java.util.HashMap;
030: import java.util.Locale;
031: import java.util.Map;
032: import javax.media.jai.ColorSpaceJAI;
033: import javax.media.jai.IHSColorSpace;
034: import javax.media.jai.ImageLayout;
035: import javax.media.jai.PointOpImage;
036: import javax.media.jai.RasterFactory;
037: import java.lang.ref.SoftReference;
038:
039: /**
040: * An <code>OpImage</code> implementing the "ColorConvert" operation as
041: * described in <code>javax.media.jai.operator.ColorConvertDescriptor</code>.
042: *
043: * @since EA4
044: *
045: * @see javax.media.jai.PointOpImage
046: * @see javax.media.jai.operator.ColorConvertDescriptor
047: *
048: */
049: final class ColorConvertOpImage extends PointOpImage {
050: /** Cache a rgb color space */
051: private static final ColorSpace rgbColorSpace = ColorSpace
052: .getInstance(ColorSpace.CS_sRGB);
053:
054: private static SoftReference softRef = null;
055:
056: /** The source image parameters */
057: private ImageParameters srcParam = null;
058:
059: /** The source image parameters */
060: private ImageParameters dstParam = null;
061:
062: /** The intermediate image parameters */
063: private ImageParameters tempParam = null;
064:
065: /** The Java 2D ColorConvertOp instance for converting integer type */
066: private ColorConvertOp colorConvertOp = null;
067:
068: /** case number */
069: private int caseNumber;
070:
071: /**
072: * Retrive/cache the ColorConvertOp. Because instantiate a ColorConvertOp
073: * is a time-consuming step, create a hashtable referred to by a
074: * SoftReference to cache the ColorConvertOp for using repeatedly.
075: *
076: * @param src the color space of the source image
077: * dst the color space of the destination image
078: * @return The ColorConvertOp to convert from the source color space to
079: * the destination color space.
080: */
081: private static synchronized ColorConvertOp getColorConvertOp(
082: ColorSpace src, ColorSpace dst) {
083: HashMap colorConvertOpBuf = null;
084:
085: if (softRef == null
086: || ((colorConvertOpBuf = (HashMap) softRef.get()) == null)) {
087:
088: colorConvertOpBuf = new HashMap();
089: softRef = new SoftReference(colorConvertOpBuf);
090: }
091:
092: ArrayList hashcode = new ArrayList(2);
093: hashcode.add(0, src);
094: hashcode.add(1, dst);
095: ColorConvertOp op = (ColorConvertOp) colorConvertOpBuf
096: .get(hashcode);
097:
098: if (op == null) {
099: op = new ColorConvertOp(src, dst, null);
100: colorConvertOpBuf.put(hashcode, op);
101: }
102:
103: return op;
104: }
105:
106: /**
107: * Retrieve the minimum value of a data type.
108: *
109: * @param dataType The data type as in DataBuffer.TYPE_*.
110: * @return The minimum value of the specified data type.
111: */
112: private static float getMinValue(int dataType) {
113: float minValue = 0;
114: switch (dataType) {
115: case DataBuffer.TYPE_BYTE:
116: minValue = 0;
117: break;
118: case DataBuffer.TYPE_SHORT:
119: minValue = Short.MIN_VALUE;
120: break;
121: case DataBuffer.TYPE_USHORT:
122: minValue = 0;
123: break;
124: case DataBuffer.TYPE_INT:
125: minValue = Integer.MIN_VALUE;
126: break;
127: default:
128: minValue = 0;
129: }
130:
131: return minValue;
132: }
133:
134: /**
135: * Retrieve the range of a data type.
136: *
137: * @param dataType The data type as in DataBuffer.TYPE_*.
138: * @return The range of the specified data type.
139: */
140: private static float getRange(int dataType) {
141: float range = 1;
142: switch (dataType) {
143: case DataBuffer.TYPE_BYTE:
144: range = 255;
145: break;
146: case DataBuffer.TYPE_SHORT:
147: range = Short.MAX_VALUE - (int) Short.MIN_VALUE;
148: break;
149: case DataBuffer.TYPE_USHORT:
150: range = Short.MAX_VALUE - (int) Short.MIN_VALUE;
151: break;
152: case DataBuffer.TYPE_INT:
153: range = Integer.MAX_VALUE - (long) Integer.MIN_VALUE;
154: break;
155: default:
156: range = 1;
157: }
158:
159: return range;
160: }
161:
162: /**
163: * Constructor.
164: *
165: * @param source The source image.
166: * @param config Configurable attributes of the image including
167: * configuration variables indexed by
168: * <code>RenderingHints.Key</code>s and image properties indexed
169: * by <code>String</code>s or <code>CaselessStringKey</code>s.
170: * This is simply forwarded to the superclass constructor.
171: * @param layout The destination image layout.
172: * @param colorModel The destination color model.
173: */
174: public ColorConvertOpImage(RenderedImage source, Map config,
175: ImageLayout layout, ColorModel colorModel) {
176: super (source, layout, config, true);
177: this .colorModel = colorModel;
178:
179: // Cache the ColorModels.
180: srcParam = new ImageParameters(source.getColorModel(), source
181: .getSampleModel());
182: dstParam = new ImageParameters(colorModel, sampleModel);
183:
184: ColorSpace srcColorSpace = srcParam.getColorModel()
185: .getColorSpace();
186: ColorSpace dstColorSpace = dstParam.getColorModel()
187: .getColorSpace();
188:
189: // for each case, define the case number; create tempParam
190: // and/or ColorConvertOp if necessary
191: if (srcColorSpace instanceof ColorSpaceJAI
192: && dstColorSpace instanceof ColorSpaceJAI) {
193:
194: // when both are ColorSpaceJAI, convert via RGB
195: caseNumber = 1;
196: tempParam = createTempParam();
197: } else if (srcColorSpace instanceof ColorSpaceJAI) {
198:
199: // when source is ColorSpaceJAI, 1. convert via RGB if
200: // the dest isn't RGB; 2. convert to RGB
201: if (dstColorSpace != rgbColorSpace) {
202: caseNumber = 2;
203: tempParam = createTempParam();
204: colorConvertOp = getColorConvertOp(rgbColorSpace,
205: dstColorSpace);
206: } else
207: caseNumber = 3;
208: } else if (dstColorSpace instanceof ColorSpaceJAI) {
209:
210: // when destination is ColorSpaceJAI, 1. convert via RGB if
211: // source isn't RGB; 2. convert from RGB
212: if (srcColorSpace != rgbColorSpace) {
213: caseNumber = 4;
214: tempParam = createTempParam();
215: colorConvertOp = getColorConvertOp(srcColorSpace,
216: rgbColorSpace);
217: } else
218: caseNumber = 5;
219: } else {
220:
221: // if all the color space are not ColorSpaceJAI
222: caseNumber = 6;
223: colorConvertOp = getColorConvertOp(srcColorSpace,
224: dstColorSpace);
225: }
226:
227: // Set flag to permit in-place operation.
228: permitInPlaceOperation();
229: }
230:
231: /**
232: * Computes a tile of the destination image in the destination color space.
233: *
234: * @param sources Cobbled sources, guaranteed to provide all the
235: * source data necessary for computing the rectangle.
236: * @param dest The tile containing the rectangle to be computed.
237: * @param destRect The rectangle within the tile to be computed.
238: */
239: protected void computeRect(Raster[] sources, WritableRaster dest,
240: Rectangle destRect) {
241: WritableRaster tempRas = null;
242:
243: // Save a reference to the source Raster.
244: Raster source = sources[0];
245:
246: // Ensure the source Raster has the same bounds as the destination.
247: if (!destRect.equals(source.getBounds())) {
248: source = source.createChild(destRect.x, destRect.y,
249: destRect.width, destRect.height, destRect.x,
250: destRect.y, null);
251: }
252:
253: switch (caseNumber) {
254: // 1. When source and destination color spaces are all ColorSpaceJAI,
255: // convert via RGB color space
256: case 1:
257: tempRas = computeRectColorSpaceJAIToRGB(source, srcParam,
258: null, tempParam);
259: computeRectColorSpaceJAIFromRGB(tempRas, tempParam, dest,
260: dstParam);
261: break;
262: // when only the source color space is ColorSpaceJAI,
263: // 2. if the destination is not RGB, convert to RGB using
264: // ColorSpaceJAI; then convert RGB to the destination
265: // 3. if the destination is RGB, convert using ColorSpaceJAI
266: case 2:
267: tempRas = computeRectColorSpaceJAIToRGB(source, srcParam,
268: null, tempParam);
269: computeRectNonColorSpaceJAI(tempRas, tempParam, dest,
270: dstParam, destRect);
271: break;
272: case 3:
273: computeRectColorSpaceJAIToRGB(source, srcParam, dest,
274: dstParam);
275: break;
276: // 4, 5. When only the destination color space is ColorSpaceJAI,
277: // similar to the case above.
278: case 4:
279: tempRas = createTempWritableRaster(source);
280: computeRectNonColorSpaceJAI(source, srcParam, tempRas,
281: tempParam, destRect);
282: computeRectColorSpaceJAIFromRGB(tempRas, tempParam, dest,
283: dstParam);
284: break;
285: case 5:
286: computeRectColorSpaceJAIFromRGB(source, srcParam, dest,
287: dstParam);
288: break;
289: // 6. If all the color space are not ColorSpaceJAI
290: case 6:
291: computeRectNonColorSpaceJAI(source, srcParam, dest,
292: dstParam, destRect);
293: default:
294: break;
295: }
296: }
297:
298: // when the source color space is ColorSpaceJAI, convert it to RGB.
299: // 1. If the source data type is short/int, shift the data to [0,
300: // MAX-MIN]
301: // 2. Convert to RGB.
302: // 3. Shift back to [MIN, MAX]
303: private WritableRaster computeRectColorSpaceJAIToRGB(Raster src,
304: ImageParameters srcParam, WritableRaster dest,
305: ImageParameters dstParam) {
306: src = convertRasterToUnsigned(src);
307:
308: ColorSpaceJAI colorSpaceJAI = (ColorSpaceJAI) srcParam
309: .getColorModel().getColorSpace();
310: dest = colorSpaceJAI.toRGB(src, srcParam.getComponentSize(),
311: dest, dstParam.getComponentSize());
312:
313: dest = convertRasterToSigned(dest);
314: return dest;
315: }
316:
317: // when the source color space is ColorSpaceJAI, convert it from RGB.
318: // 1. If the source data type is short/int, shift the data to [0,
319: // MAX-MIN]
320: // 2. Convert from RGB.
321: // 3. Shift back to [MIN, MAX]
322: private WritableRaster computeRectColorSpaceJAIFromRGB(Raster src,
323: ImageParameters srcParam, WritableRaster dest,
324: ImageParameters dstParam) {
325: src = convertRasterToUnsigned(src);
326: ColorSpaceJAI colorSpaceJAI = (ColorSpaceJAI) dstParam
327: .getColorModel().getColorSpace();
328: dest = colorSpaceJAI.fromRGB(src, srcParam.getComponentSize(),
329: dest, dstParam.getComponentSize());
330:
331: dest = convertRasterToSigned(dest);
332: return dest;
333: }
334:
335: // When the source and destination color spaces are not ColorSpaceJAI,
336: // convert using ColorConvertOp of Java 2D for integer type. For the
337: // floating point, use the following method.
338: private void computeRectNonColorSpaceJAI(Raster src,
339: ImageParameters srcParam, WritableRaster dest,
340: ImageParameters dstParam, Rectangle destRect) {
341: if (!srcParam.isFloat() && !dstParam.isFloat()) {
342: // Create a ColorConvertOp if there are only integral data.
343: // Integral type: use the ColorConvertOp.
344:
345: // Ensure that the Rasters are the same size as apparently
346: // required by ColorConvertOp although not so documented.
347: Raster s = src;
348: if (s.getMinX() != destRect.x || s.getMinY() != destRect.y
349: || s.getWidth() != destRect.width
350: || s.getHeight() != destRect.height) {
351: s = s.createChild(destRect.x, destRect.y,
352: destRect.width, destRect.height, destRect.x,
353: destRect.y, null);
354: }
355: WritableRaster d = dest;
356: if (d.getMinX() != destRect.x || d.getMinY() != destRect.y
357: || d.getWidth() != destRect.width
358: || d.getHeight() != destRect.height) {
359: d = d.createWritableChild(destRect.x, destRect.y,
360: destRect.width, destRect.height, destRect.x,
361: destRect.y, null);
362: }
363:
364: // Perform the color conversion on the (possible child) Rasters.
365: synchronized (colorConvertOp.getClass()) {
366: // Lock on the class to prevent crash in non-re-entrant
367: // native code on MP systems (jai-core issue 21).
368: colorConvertOp.filter(s, d);
369: }
370: } else {
371: //For the floating point data types, convert via CIEXYZ color space.
372: //Do it pixel-by-pixel (slow!).
373: ColorSpace srcColorSpace = srcParam.getColorModel()
374: .getColorSpace();
375: ColorSpace dstColorSpace = dstParam.getColorModel()
376: .getColorSpace();
377: boolean srcFloat = srcParam.isFloat();
378: float srcMinValue = srcParam.getMinValue();
379: float srcRange = srcParam.getRange();
380:
381: boolean dstFloat = dstParam.isFloat();
382: float dstMinValue = dstParam.getMinValue();
383: float dstRange = dstParam.getRange();
384:
385: int rectYMax = destRect.y + destRect.height;
386: int rectXMax = destRect.x + destRect.width;
387: int numComponents = srcColorSpace.getNumComponents();
388: float[] srcPixel = new float[numComponents];
389: float[] xyzPixel;
390: float[] dstPixel;
391: for (int y = destRect.y; y < rectYMax; y++) {
392: for (int x = destRect.x; x < rectXMax; x++) {
393: srcPixel = src.getPixel(x, y, srcPixel);
394: if (!srcFloat) {
395: // Normalize the source samples.
396: for (int i = 0; i < numComponents; i++) {
397: srcPixel[i] = (srcPixel[i] - srcMinValue)
398: / srcRange;
399: }
400: }
401:
402: // Convert src to dst via CIEXYZ.
403: xyzPixel = srcColorSpace.toCIEXYZ(srcPixel);
404: dstPixel = dstColorSpace.fromCIEXYZ(xyzPixel);
405:
406: if (!dstFloat) {
407: // Scale the destination samples.
408: for (int i = 0; i < numComponents; i++) {
409: dstPixel[i] = (dstPixel[i] * dstRange + dstMinValue);
410: }
411: }
412: dest.setPixel(x, y, dstPixel);
413: }
414: }
415: }
416: }
417:
418: // Back up the destination parameters. Set the destination to the
419: // bridge color space RGB.
420: private ImageParameters createTempParam() {
421: ColorModel cm = null;
422: SampleModel sm = null;
423:
424: if (srcParam.getDataType() > dstParam.getDataType()) {
425: cm = srcParam.getColorModel();
426: sm = srcParam.getSampleModel();
427: } else {
428: cm = dstParam.getColorModel();
429: sm = dstParam.getSampleModel();
430: }
431:
432: cm = new ComponentColorModel(rgbColorSpace, cm
433: .getComponentSize(), cm.hasAlpha(), cm
434: .isAlphaPremultiplied(), cm.getTransparency(), sm
435: .getDataType());
436: return new ImageParameters(cm, sm);
437: }
438:
439: // Create an WritableRaster with the same SampleModel and location
440: // as the passed Raster parameter.
441: private WritableRaster createTempWritableRaster(Raster src) {
442: Point origin = new Point(src.getMinX(), src.getMinY());
443: return RasterFactory.createWritableRaster(src.getSampleModel(),
444: origin);
445: }
446:
447: // Shift the sample value to [0, MAX-MIN]
448: private Raster convertRasterToUnsigned(Raster ras) {
449: int type = ras.getSampleModel().getDataType();
450: WritableRaster tempRas = null;
451:
452: if ((type == DataBuffer.TYPE_INT || type == DataBuffer.TYPE_SHORT)) {
453: int minX = ras.getMinX(), minY = ras.getMinY();
454: int w = ras.getWidth(), h = ras.getHeight();
455:
456: int[] buf = ras.getPixels(minX, minY, w, h, (int[]) null);
457: convertBufferToUnsigned(buf, type);
458:
459: tempRas = createTempWritableRaster(ras);
460: tempRas.setPixels(minX, minY, w, h, buf);
461: return tempRas;
462: }
463: return ras;
464: }
465:
466: // Shift the sample value back to [MIN, MAX]
467: private WritableRaster convertRasterToSigned(WritableRaster ras) {
468: int type = ras.getSampleModel().getDataType();
469: WritableRaster tempRas = null;
470:
471: if ((type == DataBuffer.TYPE_INT || type == DataBuffer.TYPE_SHORT)) {
472: int minX = ras.getMinX(), minY = ras.getMinY();
473: int w = ras.getWidth(), h = ras.getHeight();
474:
475: int[] buf = ras.getPixels(minX, minY, w, h, (int[]) null);
476: convertBufferToSigned(buf, type);
477:
478: if (ras instanceof WritableRaster)
479: tempRas = (WritableRaster) ras;
480: else
481: tempRas = createTempWritableRaster(ras);
482: tempRas.setPixels(minX, minY, w, h, buf);
483: return tempRas;
484: }
485: return ras;
486: }
487:
488: // Shift the value to [MIN, MAX]
489: private void convertBufferToSigned(int[] buf, int type) {
490: if (buf == null)
491: return;
492:
493: if (type == DataBuffer.TYPE_SHORT)
494: for (int i = 0; i < buf.length; i++) {
495: buf[i] += Short.MIN_VALUE;
496: }
497: else if (type == DataBuffer.TYPE_INT) {
498: for (int i = 0; i < buf.length; i++) {
499: buf[i] = (int) ((buf[i] & 0xFFFFFFFFl) + Integer.MIN_VALUE);
500: }
501: }
502: }
503:
504: // Shift the value to [0, MAX-MIN]
505: private void convertBufferToUnsigned(int[] buf, int type) {
506: if (buf == null)
507: return;
508:
509: if (type == DataBuffer.TYPE_SHORT)
510: for (int i = 0; i < buf.length; i++) {
511: buf[i] -= Short.MIN_VALUE;
512: }
513: else if (type == DataBuffer.TYPE_INT) {
514: for (int i = 0; i < buf.length; i++) {
515: buf[i] = (int) ((buf[i] & 0xFFFFFFFFl) - Integer.MIN_VALUE);
516: }
517: }
518: }
519:
520: // define a class to cache the parameters
521: private final class ImageParameters {
522: private boolean isFloat;
523: private ColorModel colorModel;
524: private SampleModel sampleModel;
525: private float minValue;
526: private float range;
527: private int[] componentSize;
528: private int dataType;
529:
530: ImageParameters(ColorModel cm, SampleModel sm) {
531: this .colorModel = cm;
532: this .sampleModel = sm;
533: this .dataType = sm.getDataType();
534: this .isFloat = this .dataType == DataBuffer.TYPE_FLOAT
535: || this .dataType == DataBuffer.TYPE_DOUBLE;
536: this .minValue = ColorConvertOpImage
537: .getMinValue(this .dataType);
538: this .range = ColorConvertOpImage.getRange(this .dataType);
539: this .componentSize = cm.getComponentSize();
540: }
541:
542: public boolean isFloat() {
543: return isFloat;
544: }
545:
546: public ColorModel getColorModel() {
547: return colorModel;
548: }
549:
550: public SampleModel getSampleModel() {
551: return sampleModel;
552: }
553:
554: public float getMinValue() {
555: return minValue;
556: }
557:
558: public float getRange() {
559: return range;
560: }
561:
562: public int[] getComponentSize() {
563: return componentSize;
564: }
565:
566: public int getDataType() {
567: return dataType;
568: }
569: }
570: }
|