0001: /*
0002: * $RCSfile: ColorSpaceJAI.java,v $
0003: *
0004: * Copyright (c) 2005 Sun Microsystems, Inc. All rights reserved.
0005: *
0006: * Use is subject to license terms.
0007: *
0008: * $Revision: 1.1 $
0009: * $Date: 2005/02/11 04:57:06 $
0010: * $State: Exp $
0011: */
0012: package javax.media.jai;
0013:
0014: import java.awt.Point;
0015: import java.awt.color.ColorSpace;
0016: import java.awt.color.ICC_ColorSpace;
0017: import java.awt.image.Raster;
0018: import java.awt.image.WritableRaster;
0019: import java.awt.image.DataBuffer;
0020: import java.awt.image.DataBufferByte;
0021: import java.awt.image.DataBufferShort;
0022: import java.awt.image.DataBufferInt;
0023: import java.awt.image.SampleModel;
0024:
0025: /**
0026: * An abstract subclass of <code>ColorSpace</code> which adds methods to
0027: * transform colors represented as pixels in a <code>Raster</code> between
0028: * a specific color space and either sRGB or a well-defined C.I.E. X,Y,Z
0029: * color space. As mentioned in the documentation of {@link ColorSpace},
0030: * sRGB is a proposed standard default RGB color space for the Internet.
0031: *
0032: * <p>This class is particularly applicable for use with color
0033: * spaces which are mathematically defined and for which no I.C.C. profile
0034: * is readily available. (Note however that color conversions specified
0035: * by a simple matrix transformation might best be effected using the
0036: * "BandCombine" operation.) The JAI "ColorConvert" operation recognizes when
0037: * an instance of <code>ColorSpaceJAI</code> is present and uses the
0038: * <code>Raster</code>-based conversion methods to improve performance.
0039: * This is possible because without the <code>ColorSpaceJAI</code> definition,
0040: * a <code>ColorSpace</code> which was not an {@link ICC_ColorSpace}
0041: * would permit color conversion only by means of pixel-by-pixel invocations
0042: * of <code>toCIEXYZ(float[])</code> and <code>fromCIEXYZ(float[])</code> (or,
0043: * equivalently <code>toRGB(float[])</code> and
0044: * <code>fromRGB(float[])</code>).</p>
0045: *
0046: * @see java.awt.color.ColorSpace
0047: * @see java.awt.color.ICC_ColorSpace
0048: * @see javax.media.jai.operator.ColorConvertDescriptor
0049: * @see javax.media.jai.operator.BandCombineDescriptor
0050: *
0051: * @since JAI 1.1
0052: */
0053: public abstract class ColorSpaceJAI extends ColorSpace {
0054: /** Cache the maximum value for XYZ color space. */
0055: private static final double maxXYZ = 1 + 32767.0 / 32768.0;
0056:
0057: /** Cache the power value for XYZ to RGB */
0058: private static final double power1 = 1.0 / 2.4;
0059:
0060: /** The map from byte RGB to the step before matrix operation. */
0061: private static double[] LUT = new double[256];
0062:
0063: static {
0064: for (int i = 0; i < 256; i++) {
0065: double v = i / 255.0;
0066: if (v < 0.040449936)
0067: LUT[i] = v / 12.92;
0068: else
0069: LUT[i] = Math.pow((v + 0.055) / 1.055, 2.4);
0070: }
0071: }
0072:
0073: /**
0074: * Whether conversion to/from this <code>ColorSpaceJAI</code>
0075: * is more efficient using the sRGB methods.
0076: */
0077: private boolean isRGBPreferredIntermediary;
0078:
0079: /**
0080: * Transforms the pixel data in the source <code>Raster</code> from
0081: * CIEXYZ to sRGB. It is assumed that the input XYZ values are
0082: * represented relative to the CIE D50 white point of the
0083: * <code>ColorSpace.CS_CIEXYZ</code> color space. Integral data will
0084: * be normalized according to the number of bits specified for the
0085: * respective component; floating point data should be between 0.0 and
0086: * 1.0 + (32767.0 / 32768.0). All integral data
0087: * are assumed to be unsigned; signed data should be shifted by the
0088: * caller before invoking this method.
0089: *
0090: * <p> The exact sequence of transformations applied is as follows:</p>
0091: * <p><ol>
0092: * <li>If the source data are integral, convert the digital codes to
0093: * CIE XYZ values in the range <code>[0.0, F<sub>max</sub>]</code>
0094: * as:
0095: * <pre>
0096: * F<sub>d50</sub> = F<sub>max</sub> * I<sub>src</sub> / 2<sup>M<sub>src</sub></sup>
0097: * </pre>
0098: *
0099: * where
0100: *
0101: * <pre>
0102: * I<sub>src</sub> is the digital code of a source color component
0103: * M<sub>src</sub> is the number of significant bits per source color
0104: * component
0105: * F<sub>max</sub> is 1.0 + (32767.0 / 32768.0)
0106: * F<sub>d50</sub> is the corresponding CIE XYZ value relative to CIE D50
0107: * </pre>
0108: *
0109: * If the source data are floating point no scaling is performed and it
0110: * is assumed that the data are already clipped to the range
0111: * <code>[0.0, 1.0 + (32767.0 / 32768.0)]</code>.</li>
0112: * <p><li>Perform chromatic adaptation from the CIE D50 white point to the
0113: * CIE D65 white point as described in {@link ICC_ColorSpace#fromCIEXYZ}:
0114: * <pre>
0115: * X<sub>d65</sub> = X<sub>d50</sub> * (X<sub>wd65</sub> / X<sub>wd50</sub>)
0116: * Y<sub>d65</sub> = Y<sub>d50</sub> * (Y<sub>wd65</sub> / Y<sub>wd50</sub>)
0117: * Z<sub>d65</sub> = Z<sub>d50</sub> * (Z<sub>wd65</sub> / Z<sub>wd50</sub>)
0118: * </pre>
0119: *
0120: * where
0121: *
0122: * <pre>
0123: * X<sub>d50</sub>, Y<sub>d50</sub>, Z<sub>d50</sub> are the XYZ values relative to CIE D50
0124: * X<sub>wd65</sub>, Y<sub>wd65</sub>, Z<sub>wd65</sub> are the CIE D65 white point values
0125: * X<sub>wd50</sub>, Y<sub>wd50</sub>, Z<sub>wd50</sub> are the CIE D50 white point values
0126: * X<sub>d65</sub>, Y<sub>d65</sub>, Z<sub>d65</sub> are the XYZ values relative to CIE D65
0127: * </pre>
0128: *
0129: * Substituting the actual CIE D50 and D65 white point values in the
0130: * above gives:
0131: *
0132: * <pre>
0133: * X<sub>d65</sub> = X<sub>d50</sub> * (0.3127/0.3457)
0134: * Y<sub>d65</sub> = Y<sub>d50</sub> * (0.3291/0.3585)
0135: * Z<sub>d65</sub> = Z<sub>d50</sub> * (0.3582/0.2958)
0136: * </pre></li></p>
0137: * <li>Calculate sRGB tristimulus values as:
0138: * <pre>
0139: * [ R<sub>sRGB</sub> ] [ 3.2406 -1.5372 -0.4986 ] [ X<sub>d65</sub> ]
0140: * [ G<sub>sRGB</sub> ] = [ -0.9689 1.8758 0.0415 ] [ Y<sub>d65</sub> ]
0141: * [ B<sub>sRGB</sub> ] [ 0.0557 -0.2040 1.0570 ] [ Z<sub>d65</sub> ]
0142: * </pre></li>
0143: * <p><li>
0144: * Clip sRGB tristimulus values to the range <code>[0.0, 1.0]</code>.
0145: * </li></p>
0146: * <li>Transform sRGB tristimulus values to non-linear sR'G'B' values as:
0147: * <pre>
0148: * C'<sub>sRGB</sub> = 12.92*C<sub>sRGB</sub> if C<sub>sRGB</sub> <= 0.0031308
0149: * C'<sub>sRGB</sub> = 1.055*C<sub>sRGB</sub><sup>(1.0/2.4)</sup> - 0.055 if C<sub>sRGB</sub> > 0.0031308
0150: * </pre>
0151: *
0152: * where
0153: *
0154: * <pre>
0155: * C<sub>sRGB</sub> is a sRGB tristimulus value
0156: * C'<sub>sRGB</sub> is the corresponding non-linear sR'G'B' value
0157: * </pre></li>
0158: * <li>If the destination data are integral, convert the non-linear
0159: * sR'G'B' values to digital codes as:
0160: * <pre>
0161: * I<sub>dest</sub> = round(C'<sub>sRGB</sub> * 2<sup>M<sub>dest</sub></sup>)
0162: * </pre>
0163: *
0164: * where
0165: *
0166: * <pre>
0167: * C'<sub>sRGB</sub> is the a non-linear sR'G'B' value
0168: * M<sub>dest</sub> is the number of significant bits per destination color
0169: * component
0170: * I<sub>dest</sub> is the digital code of a destination color component
0171: * </pre>
0172: * If the destination data are floating point neither scaling nor rounding
0173: * is performed.
0174: * </li>
0175: * </ol></p>
0176: *
0177: * <p>If the destination <code>WritableRaster</code> is <code>null</code>,
0178: * a new <code>WritableRaster</code> will be created. The
0179: * <code>Raster</code>s are treated as having no alpha channel, i.e.,
0180: * all bands are color bands.</p>
0181: *
0182: * <p> This method is provided for the convenience of extenders defining
0183: * a color space for which the conversion is defined with respect to
0184: * CIEXYZ.</p>
0185: *
0186: * <p><i> It should be noted that there is no official specification
0187: * of sRGB with respect to digital codes of depths other than 8 bits.
0188: * The present implementation for other bit depths is provided as an
0189: * extrapolation of the 8-bit sRGB standard and its use should be
0190: * recognized as such. The extrapolation is implemented by replacing
0191: * the white digital count (<code>WDC</code>) and black digital count
0192: * (<code>KDC</code>) in the 8-bit sRGB specification with values
0193: * corresponding to the extrema of the data type in question when
0194: * treated as unsigned. For all data types <code>KDC</code> is zero
0195: * and <code>WDC</code> is specified by the component size
0196: * parameters.</i></p>
0197: *
0198: * @param src the source <code>Raster</code> to be converted.
0199: * @param srcComponentSize array that specifies the number of significant
0200: * bits per source color component; ignored for floating point data.
0201: * If <code>null</code> defaults to the value returned by
0202: * <code>src.getSampleModel().getSampleSize()</code>.
0203: * @param dest the destination <code>WritableRaster</code>,
0204: * or <code>null</code>.
0205: * @param destComponentSize array that specifies the number of significant
0206: * bits per destination color component; ignored for floating point
0207: * data. If <code>null</code>, defaults to the value returned by
0208: * <code>dest.getSampleModel().getSampleSize()</code>, or the sample
0209: * size of the newly created destination WritableRaster if dest is
0210: * null.
0211: * @return <code>dest</code> color converted from <code>src</code>
0212: * or a new, <code>WritableRaster</code> containing the converted
0213: * pixels if <code>dest</code> is <code>null</code>.
0214: * @exception IllegalArgumentException if <code>src</code> is
0215: * <code>null</code>, the number of source or destination
0216: * bands is not 3, or either component size array
0217: * is non-null and has length not equal to 3.
0218: */
0219: public static WritableRaster CIEXYZToRGB(Raster src,
0220: int[] srcComponentSize, WritableRaster dest,
0221: int[] destComponentSize) {
0222:
0223: // Validate the parameters
0224: checkParameters(src, srcComponentSize, dest, destComponentSize);
0225:
0226: SampleModel srcSampleModel = src.getSampleModel();
0227:
0228: /*if the parameter srcComponentSize is null, use the sample size
0229: * of the source raster.
0230: */
0231: if (srcComponentSize == null)
0232: srcComponentSize = srcSampleModel.getSampleSize();
0233:
0234: // if the destination raster is null, create a new WritableRaster
0235: if (dest == null) {
0236: Point origin = new Point(src.getMinX(), src.getMinY());
0237: dest = RasterFactory.createWritableRaster(srcSampleModel,
0238: origin);
0239: }
0240:
0241: /* if the parameter dstComponentSize is null, use the sample size
0242: * of the source raster.
0243: */
0244: SampleModel dstSampleModel = dest.getSampleModel();
0245: if (destComponentSize == null)
0246: destComponentSize = dstSampleModel.getSampleSize();
0247:
0248: PixelAccessor srcAcc = new PixelAccessor(srcSampleModel, null);
0249: UnpackedImageData srcUid = srcAcc.getPixels(src, src
0250: .getBounds(), srcSampleModel.getDataType(), false);
0251:
0252: switch (srcSampleModel.getDataType()) {
0253:
0254: case DataBuffer.TYPE_BYTE:
0255: CIEXYZToRGBByte(srcUid, srcComponentSize, dest,
0256: destComponentSize);
0257: break;
0258: case DataBuffer.TYPE_USHORT:
0259: case DataBuffer.TYPE_SHORT:
0260: CIEXYZToRGBShort(srcUid, srcComponentSize, dest,
0261: destComponentSize);
0262: break;
0263: case DataBuffer.TYPE_INT:
0264: CIEXYZToRGBInt(srcUid, srcComponentSize, dest,
0265: destComponentSize);
0266: break;
0267: case DataBuffer.TYPE_FLOAT:
0268: CIEXYZToRGBFloat(srcUid, srcComponentSize, dest,
0269: destComponentSize);
0270: break;
0271: case DataBuffer.TYPE_DOUBLE:
0272: CIEXYZToRGBDouble(srcUid, srcComponentSize, dest,
0273: destComponentSize);
0274: break;
0275: }
0276:
0277: return dest;
0278: }
0279:
0280: /**
0281: * Verify that the parameters are compatible with the limitations
0282: * of the static methods {@link #CIEXYZToRGB} and {@link #RGBToCIEXYZ}.
0283: * If any of the parameters are not compatible with the requirements
0284: * of these methods, throw an <code>IllegalArgumentException</code>
0285: *
0286: * @throws IllegalArgumentException if <code>src</code> is
0287: * <code>null</code>.
0288: * @throws IllegalArgumentException if <code>src.getNumBands()</code>
0289: * does not return the value 3.
0290: * @throws IllegalArgumentException if <code>dst</code> is
0291: * non-<code>null</code> and <code>dst.getNumBands()</code>
0292: * does not return the value 3.
0293: * @throws IllegalArgumentException if <code>srcComponentSize</code> is
0294: * non-<code>null</code> but its length is not 3.
0295: * @throws IllegalArgumentException if <code>destComponentSize</code> is
0296: * non-<code>null</code> but its length is not 3.
0297: */
0298: protected static void checkParameters(Raster src,
0299: int[] srcComponentSize, WritableRaster dest,
0300: int[] destComponentSize) {
0301:
0302: if (src == null)
0303: throw new IllegalArgumentException(JaiI18N
0304: .getString("ColorSpaceJAI0"));
0305: if (src.getNumBands() != 3)
0306: throw new IllegalArgumentException(JaiI18N
0307: .getString("ColorSpaceJAI1"));
0308: if (dest != null && dest.getNumBands() != 3)
0309: throw new IllegalArgumentException(JaiI18N
0310: .getString("ColorSpaceJAI2"));
0311: if (srcComponentSize != null && srcComponentSize.length != 3)
0312: throw new IllegalArgumentException(JaiI18N
0313: .getString("ColorSpaceJAI3"));
0314: if (destComponentSize != null && destComponentSize.length != 3)
0315: throw new IllegalArgumentException(JaiI18N
0316: .getString("ColorSpaceJAI4"));
0317: }
0318:
0319: /**
0320: * After conversion, the range of a signed short is
0321: * <code>[0, Short.MAX_Value-Short.MIN_VALUE]</code> and that
0322: * of an integer is <code>[0, 0xFFFFFFFFL]</code>. To avoid
0323: * clamping, convert the value to a signed integer with the same
0324: * binary bits. If the <code>dataType</code> parameter is either
0325: * <code>DataBuffer.TYPE_SHORT</code> or <code>DataBuffer.TYPE_INT</code>
0326: * then the array is modified in place; otherwise the method has no
0327: * effect.
0328: *
0329: * @param buf Array of post-conversion digital codes.
0330: * @param dataType The data type: one of the constants <code>TYPE_*</code>
0331: * defined in {@link DataBuffer}.
0332: */
0333: static void convertToSigned(double[] buf, int dataType) {
0334: if (dataType == DataBuffer.TYPE_SHORT) {
0335: for (int i = 0; i < buf.length; i++) {
0336: short temp = (short) (((int) buf[i]) & 0xFFFF);
0337: buf[i] = temp;
0338: }
0339: } else if (dataType == DataBuffer.TYPE_INT) {
0340: for (int i = 0; i < buf.length; i++) {
0341: int temp = (int) (((long) buf[i]) & 0xFFFFFFFFl);
0342: buf[i] = temp;
0343: }
0344: }
0345: }
0346:
0347: static void XYZ2RGB(float[] XYZ, float[] RGB) {
0348: RGB[0] = 2.9311227F * XYZ[0] - 1.4111496F * XYZ[1] - 0.6038046F
0349: * XYZ[2];
0350: RGB[1] = -0.87637005F * XYZ[0] + 1.7219844F * XYZ[1]
0351: + 0.0502565F * XYZ[2];
0352: RGB[2] = 0.05038065F * XYZ[0] - 0.187272F * XYZ[1] + 1.280027F
0353: * XYZ[2];
0354:
0355: for (int i = 0; i < 3; i++) {
0356: float v = RGB[i];
0357:
0358: if (v < 0.0F)
0359: v = 0.0F;
0360:
0361: if (v < 0.0031308F)
0362: RGB[i] = 12.92F * v;
0363: else {
0364: if (v > 1.0F)
0365: v = 1.0F;
0366:
0367: RGB[i] = (float) (1.055 * Math.pow(v, power1) - 0.055);
0368: }
0369: }
0370: }
0371:
0372: private static void roundValues(double[] data) {
0373: for (int i = 0; i < data.length; i++)
0374: data[i] = (long) (data[i] + 0.5);
0375: }
0376:
0377: // Convert a byte raster from CIEXYZ to RGB color space
0378: static void CIEXYZToRGBByte(UnpackedImageData src,
0379: int[] srcComponentSize, WritableRaster dest,
0380: int[] destComponentSize) {
0381: byte[] xBuf = src.getByteData(0);
0382: byte[] yBuf = src.getByteData(1);
0383: byte[] zBuf = src.getByteData(2);
0384:
0385: // maps the integral XYZ into floating-point
0386: float normx = (float) (maxXYZ / ((1L << srcComponentSize[0]) - 1));
0387: float normy = (float) (maxXYZ / ((1L << srcComponentSize[1]) - 1));
0388: float normz = (float) (maxXYZ / ((1L << srcComponentSize[2]) - 1));
0389:
0390: // the upper bounds for the red, green and blue bands
0391: double upperr = 1.0, upperg = 1.0, upperb = 1.0;
0392:
0393: int dstType = dest.getSampleModel().getDataType();
0394:
0395: // for the integer type, re-calculate the bounds
0396: if (dstType < DataBuffer.TYPE_FLOAT) {
0397: upperr = (1L << destComponentSize[0]) - 1;
0398: upperg = (1L << destComponentSize[1]) - 1;
0399: upperb = (1L << destComponentSize[2]) - 1;
0400: }
0401:
0402: int height = dest.getHeight();
0403: int width = dest.getWidth();
0404:
0405: double[] dstPixels = new double[3 * height * width];
0406:
0407: int xStart = src.bandOffsets[0];
0408: int yStart = src.bandOffsets[1];
0409: int zStart = src.bandOffsets[2];
0410: int srcPixelStride = src.pixelStride;
0411: int srcLineStride = src.lineStride;
0412:
0413: float[] XYZ = new float[3];
0414: float[] RGB = new float[3];
0415:
0416: int dIndex = 0;
0417: for (int j = 0; j < height; j++, xStart += srcLineStride, yStart += srcLineStride, zStart += srcLineStride) {
0418: for (int i = 0, xIndex = xStart, yIndex = yStart, zIndex = zStart; i < width; i++, xIndex += srcPixelStride, yIndex += srcPixelStride, zIndex += srcPixelStride) {
0419: XYZ[0] = (xBuf[xIndex] & 0xFF) * normx;
0420: XYZ[1] = (yBuf[yIndex] & 0xFF) * normy;
0421: XYZ[2] = (zBuf[zIndex] & 0xFF) * normz;
0422:
0423: XYZ2RGB(XYZ, RGB);
0424:
0425: dstPixels[dIndex++] = upperr * RGB[0];
0426: dstPixels[dIndex++] = upperg * RGB[1];
0427: dstPixels[dIndex++] = upperb * RGB[2];
0428: }
0429: }
0430:
0431: // Because of 4738524: setPixels should round the provided double
0432: // value instead of casting
0433: // If it is fixed, then this piece of code can be removed.
0434: if (dstType < DataBuffer.TYPE_FLOAT)
0435: roundValues(dstPixels);
0436:
0437: convertToSigned(dstPixels, dstType);
0438: dest.setPixels(dest.getMinX(), dest.getMinY(), width, height,
0439: dstPixels);
0440: }
0441:
0442: // convert a short type raster from CIEXYZ to RGB
0443: private static void CIEXYZToRGBShort(UnpackedImageData src,
0444: int[] srcComponentSize, WritableRaster dest,
0445: int[] destComponentSize) {
0446: short[] xBuf = src.getShortData(0);
0447: short[] yBuf = src.getShortData(1);
0448: short[] zBuf = src.getShortData(2);
0449:
0450: // maps the integral XYZ into floating-point
0451: float normx = (float) (maxXYZ / ((1L << srcComponentSize[0]) - 1));
0452: float normy = (float) (maxXYZ / ((1L << srcComponentSize[1]) - 1));
0453: float normz = (float) (maxXYZ / ((1L << srcComponentSize[2]) - 1));
0454:
0455: // the upper bounds for the red, green and blue bands
0456: double upperr = 1.0, upperg = 1.0, upperb = 1.0;
0457:
0458: int dstType = dest.getSampleModel().getDataType();
0459:
0460: // for the integer type, re-calculate the norm and bands
0461: if (dstType < DataBuffer.TYPE_FLOAT) {
0462: upperr = (1L << destComponentSize[0]) - 1;
0463: upperg = (1L << destComponentSize[1]) - 1;
0464: upperb = (1L << destComponentSize[2]) - 1;
0465: }
0466:
0467: int height = dest.getHeight();
0468: int width = dest.getWidth();
0469:
0470: double[] dstPixels = new double[3 * height * width];
0471:
0472: int xStart = src.bandOffsets[0];
0473: int yStart = src.bandOffsets[1];
0474: int zStart = src.bandOffsets[2];
0475: int srcPixelStride = src.pixelStride;
0476: int srcLineStride = src.lineStride;
0477:
0478: float[] XYZ = new float[3];
0479: float[] RGB = new float[3];
0480:
0481: int dIndex = 0;
0482: for (int j = 0; j < height; j++, xStart += srcLineStride, yStart += srcLineStride, zStart += srcLineStride) {
0483: for (int i = 0, xIndex = xStart, yIndex = yStart, zIndex = zStart; i < width; i++, xIndex += srcPixelStride, yIndex += srcPixelStride, zIndex += srcPixelStride) {
0484: XYZ[0] = (xBuf[xIndex] & 0xFFFF) * normx;
0485: XYZ[1] = (yBuf[yIndex] & 0xFFFF) * normy;
0486: XYZ[2] = (zBuf[zIndex] & 0xFFFF) * normz;
0487:
0488: XYZ2RGB(XYZ, RGB);
0489:
0490: dstPixels[dIndex++] = upperr * RGB[0];
0491: dstPixels[dIndex++] = upperg * RGB[1];
0492: dstPixels[dIndex++] = upperb * RGB[2];
0493: }
0494: }
0495:
0496: // Because of 4738524: setPixels should round the provided double
0497: // value instead of casting
0498: // If it is fixed, then this piece of code can be removed.
0499: if (dstType < DataBuffer.TYPE_FLOAT)
0500: roundValues(dstPixels);
0501:
0502: convertToSigned(dstPixels, dstType);
0503: dest.setPixels(dest.getMinX(), dest.getMinY(), width, height,
0504: dstPixels);
0505: }
0506:
0507: // convert an int type raster from CIEXYZ to RGB
0508: private static void CIEXYZToRGBInt(UnpackedImageData src,
0509: int[] srcComponentSize, WritableRaster dest,
0510: int[] destComponentSize) {
0511: int[] xBuf = src.getIntData(0);
0512: int[] yBuf = src.getIntData(1);
0513: int[] zBuf = src.getIntData(2);
0514:
0515: // maps the integral XYZ into floating-point
0516: float normx = (float) (maxXYZ / ((1L << srcComponentSize[0]) - 1));
0517: float normy = (float) (maxXYZ / ((1L << srcComponentSize[1]) - 1));
0518: float normz = (float) (maxXYZ / ((1L << srcComponentSize[2]) - 1));
0519:
0520: // the upper bound for each band
0521: double upperr = 1.0, upperg = 1.0, upperb = 1.0;
0522:
0523: int dstType = dest.getSampleModel().getDataType();
0524:
0525: // for the integer type, re-calculate the bounds
0526: if (dstType < DataBuffer.TYPE_FLOAT) {
0527: upperr = (1L << destComponentSize[0]) - 1;
0528: upperg = (1L << destComponentSize[1]) - 1;
0529: upperb = (1L << destComponentSize[2]) - 1;
0530: }
0531:
0532: int height = dest.getHeight();
0533: int width = dest.getWidth();
0534:
0535: double[] dstPixels = new double[3 * height * width];
0536:
0537: int xStart = src.bandOffsets[0];
0538: int yStart = src.bandOffsets[1];
0539: int zStart = src.bandOffsets[2];
0540: int srcPixelStride = src.pixelStride;
0541: int srcLineStride = src.lineStride;
0542:
0543: float[] XYZ = new float[3];
0544: float[] RGB = new float[3];
0545:
0546: int dIndex = 0;
0547: for (int j = 0; j < height; j++, xStart += srcLineStride, yStart += srcLineStride, zStart += srcLineStride) {
0548: for (int i = 0, xIndex = xStart, yIndex = yStart, zIndex = zStart; i < width; i++, xIndex += srcPixelStride, yIndex += srcPixelStride, zIndex += srcPixelStride) {
0549: XYZ[0] = (xBuf[xIndex] & 0xFFFFFFFFl) * normx;
0550: XYZ[1] = (yBuf[yIndex] & 0xFFFFFFFFl) * normy;
0551: XYZ[2] = (zBuf[zIndex] & 0xFFFFFFFFl) * normz;
0552:
0553: XYZ2RGB(XYZ, RGB);
0554:
0555: dstPixels[dIndex++] = upperr * RGB[0];
0556: dstPixels[dIndex++] = upperg * RGB[1];
0557: dstPixels[dIndex++] = upperb * RGB[2];
0558: }
0559: }
0560:
0561: // Because of 4738524: setPixels should round the provided double
0562: // value instead of casting
0563: // If it is fixed, then this piece of code can be removed.
0564: if (dstType < DataBuffer.TYPE_FLOAT)
0565: roundValues(dstPixels);
0566:
0567: convertToSigned(dstPixels, dstType);
0568: dest.setPixels(dest.getMinX(), dest.getMinY(), width, height,
0569: dstPixels);
0570: }
0571:
0572: // convert a float type ratser from CIEXYZ to RGB
0573: private static void CIEXYZToRGBFloat(UnpackedImageData src,
0574: int[] srcComponentSize, WritableRaster dest,
0575: int[] destComponentSize) {
0576: float[] xBuf = src.getFloatData(0);
0577: float[] yBuf = src.getFloatData(1);
0578: float[] zBuf = src.getFloatData(2);
0579:
0580: // the upper bounds for the 3 bands
0581: double upperr = 1.0, upperg = 1.0, upperb = 1.0;
0582:
0583: int dstType = dest.getSampleModel().getDataType();
0584:
0585: // for the integer type, re-calculate the bounds
0586: if (dstType < DataBuffer.TYPE_FLOAT) {
0587: upperr = (1L << destComponentSize[0]) - 1;
0588: upperg = (1L << destComponentSize[1]) - 1;
0589: upperb = (1L << destComponentSize[2]) - 1;
0590: }
0591:
0592: int height = dest.getHeight();
0593: int width = dest.getWidth();
0594:
0595: double[] dstPixels = new double[3 * height * width];
0596:
0597: int xStart = src.bandOffsets[0];
0598: int yStart = src.bandOffsets[1];
0599: int zStart = src.bandOffsets[2];
0600: int srcPixelStride = src.pixelStride;
0601: int srcLineStride = src.lineStride;
0602:
0603: float[] XYZ = new float[3];
0604: float[] RGB = new float[3];
0605:
0606: int dIndex = 0;
0607: for (int j = 0; j < height; j++, xStart += srcLineStride, yStart += srcLineStride, zStart += srcLineStride) {
0608: for (int i = 0, xIndex = xStart, yIndex = yStart, zIndex = zStart; i < width; i++, xIndex += srcPixelStride, yIndex += srcPixelStride, zIndex += srcPixelStride) {
0609: XYZ[0] = xBuf[xIndex];
0610: XYZ[1] = yBuf[yIndex];
0611: XYZ[2] = zBuf[zIndex];
0612:
0613: XYZ2RGB(XYZ, RGB);
0614:
0615: dstPixels[dIndex++] = upperr * RGB[0];
0616: dstPixels[dIndex++] = upperg * RGB[1];
0617: dstPixels[dIndex++] = upperb * RGB[2];
0618: }
0619: }
0620:
0621: // Because of 4738524: setPixels should round the provided double
0622: // value instead of casting
0623: // If it is fixed, then this piece of code can be removed.
0624: if (dstType < DataBuffer.TYPE_FLOAT)
0625: roundValues(dstPixels);
0626:
0627: convertToSigned(dstPixels, dstType);
0628: dest.setPixels(dest.getMinX(), dest.getMinY(), width, height,
0629: dstPixels);
0630: }
0631:
0632: // convert a double type ratser form CIEXYZ to RGB color space
0633: private static void CIEXYZToRGBDouble(UnpackedImageData src,
0634: int[] srcComponentSize, WritableRaster dest,
0635: int[] destComponentSize) {
0636: double[] xBuf = src.getDoubleData(0);
0637: double[] yBuf = src.getDoubleData(1);
0638: double[] zBuf = src.getDoubleData(2);
0639:
0640: // the upper bound of each band
0641: double upperr = 1.0, upperg = 1.0, upperb = 1.0;
0642:
0643: int dstType = dest.getSampleModel().getDataType();
0644:
0645: // for the integer type, re-calculate the bounds
0646: if (dstType < DataBuffer.TYPE_FLOAT) {
0647: upperr = (1L << destComponentSize[0]) - 1;
0648: upperg = (1L << destComponentSize[1]) - 1;
0649: upperb = (1L << destComponentSize[2]) - 1;
0650: }
0651:
0652: int height = dest.getHeight();
0653: int width = dest.getWidth();
0654:
0655: double[] dstPixels = new double[3 * height * width];
0656:
0657: int xStart = src.bandOffsets[0];
0658: int yStart = src.bandOffsets[1];
0659: int zStart = src.bandOffsets[2];
0660: int srcPixelStride = src.pixelStride;
0661: int srcLineStride = src.lineStride;
0662:
0663: float[] XYZ = new float[3];
0664: float[] RGB = new float[3];
0665:
0666: int dIndex = 0;
0667: for (int j = 0; j < height; j++, xStart += srcLineStride, yStart += srcLineStride, zStart += srcLineStride) {
0668: for (int i = 0, xIndex = xStart, yIndex = yStart, zIndex = zStart; i < width; i++, xIndex += srcPixelStride, yIndex += srcPixelStride, zIndex += srcPixelStride) {
0669: XYZ[0] = (float) xBuf[xIndex];
0670: XYZ[1] = (float) yBuf[yIndex];
0671: XYZ[2] = (float) zBuf[zIndex];
0672:
0673: XYZ2RGB(XYZ, RGB);
0674:
0675: dstPixels[dIndex++] = upperr * RGB[0];
0676: dstPixels[dIndex++] = upperg * RGB[1];
0677: dstPixels[dIndex++] = upperb * RGB[2];
0678: }
0679: }
0680:
0681: // Because of 4738524: setPixels should round the provided double
0682: // value instead of casting
0683: // If it is fixed, then this piece of code can be removed.
0684: if (dstType < DataBuffer.TYPE_FLOAT)
0685: roundValues(dstPixels);
0686:
0687: convertToSigned(dstPixels, dstType);
0688: dest.setPixels(dest.getMinX(), dest.getMinY(), width, height,
0689: dstPixels);
0690: }
0691:
0692: /**
0693: * Transforms the pixel data in the source <code>Raster</code> from
0694: * sRGB to CIEXYZ. The output XYZ values are represented relative to
0695: * the CIE D50 white point of the <code>ColorSpace.CS_CIEXYZ</code>
0696: * color space. Integral data will be normalized according to the
0697: * number of bits specified for the respective component; floating
0698: * point data should be between 0.0 and 1.0. All integral data are
0699: * assumed to be unsigned; signed data should be shifted by the caller
0700: * before invoking this method.
0701: *
0702: * <p> The exact sequence of transformations applied is as follows:</p>
0703: * <p><ol>
0704: * <li>If the source data are integral, convert the digital codes to
0705: * non-linear sRGB values in the range <code>[0.0, 1.0]</code> as:
0706: * <pre>
0707: * C'<sub>sRGB</sub> = I<sub>src</sub> / 2<sup>M<sub>src</sub></sup>
0708: * </pre>
0709: *
0710: * where
0711: *
0712: * <pre>
0713: * I<sub>src</sub> is the digital code of a source color component
0714: * M<sub>src</sub> is the number of significant bits per source color
0715: * component
0716: * C'<sub>sRGB</sub> is the corresponding sR'G'B' value
0717: * </pre>
0718: *
0719: * If the source data are floating point no scaling is performed and it
0720: * is assumed that the data are already clipped to the range
0721: * <code>[0.0, 1.0]</code>.</li>
0722: * <p><li>Transform non-linear sR'G'B' values to sRGB tristimulus values
0723: * as:
0724: * <pre>
0725: * C<sub>sRGB</sub> = C'<sub>sRGB</sub>/12.92 if C'<sub>sRGB</sub> <= 0.04045
0726: * C<sub>sRGB</sub> = [(C'<sub>sRGB</sub> + 0.055)/1.055]<sup>2.4</sup> if C'<sub>sRGB</sub> > 0.04045
0727: * </pre>
0728: *
0729: * where
0730: *
0731: * <pre>
0732: * C'<sub>sRGB</sub> is a non-linear sR'G'B' value
0733: * C<sub>sRGB</sub> is the corresponding sRGB tristimulus value
0734: * </pre></li></p>
0735: * <li>Calculate CIE XYZ D65-relative values as:
0736: * <pre>
0737: * [ X<sub>d65</sub> ] [ 0.4124 0.3576 0.1805 ] [ R<sub>sRGB</sub> ]
0738: * [ Y<sub>d65</sub> ] = [ 0.2126 0.7152 0.0722 ] [ G<sub>sRGB</sub> ]
0739: * [ Z<sub>d65</sub> ] [ 0.0193 0.1192 0.9505 ] [ B<sub>sRGB</sub> ]
0740: * </pre></li>
0741: * <li>Perform chromatic adaptation from the CIE D65 white point to the
0742: * CIE D50 white point as described in {@link ICC_ColorSpace#toCIEXYZ}:
0743: * <pre>
0744: * X<sub>d50</sub> = X<sub>d65</sub> * (X<sub>wd50</sub> / X<sub>wd65</sub>)
0745: * Y<sub>d50</sub> = Y<sub>d65</sub> * (Y<sub>wd50</sub> / Y<sub>wd65</sub>)
0746: * Z<sub>d50</sub> = Z<sub>d65</sub> * (Z<sub>wd50</sub> / Z<sub>wd65</sub>)
0747: * </pre>
0748: *
0749: * where
0750: *
0751: * <pre>
0752: * X<sub>d65</sub>, Y<sub>d65</sub>, Z<sub>d65</sub> are the XYZ values relative to CIE D65
0753: * X<sub>wd50</sub>, Y<sub>wd50</sub>, Z<sub>wd50</sub> are the CIE D50 white point values
0754: * X<sub>wd65</sub>, Y<sub>wd65</sub>, Z<sub>wd65</sub> are the CIE D65 white point values
0755: * X<sub>d50</sub>, Y<sub>d50</sub>, Z<sub>d50</sub> are the XYZ values relative to CIE D50
0756: * </pre>
0757: *
0758: * Substituting the actual CIE D50 and D65 white point values in the
0759: * above gives:
0760: *
0761: * <pre>
0762: * X<sub>d50</sub> = X<sub>d65</sub> * (0.3457/0.3127)
0763: * Y<sub>d50</sub> = Y<sub>d65</sub> * (0.3585/0.3291)
0764: * Z<sub>d50</sub> = Z<sub>d65</sub> * (0.2958/0.3582)
0765: * </pre></li>
0766: * <li>If the destination data are integral, convert the CIE XYZ
0767: * values to digital codes as:
0768: * <pre>
0769: * I<sub>dest</sub> = round(F<sub>d50</sub> * 2<sup>M<sub>dest</sub></sup> / F<sub>max</sub>)
0770: * </pre>
0771: *
0772: * where
0773: *
0774: * <pre>
0775: * F<sub>d50</sub> is a CIE XYZ value relative to CIE D50
0776: * M<sub>dest</sub> is the number of significant bits per destination color
0777: * component
0778: * F<sub>max</sub> is 1.0 + (32767.0 / 32768.0)
0779: * I<sub>dest</sub> is the digital code of a destination color component
0780: * </pre>
0781: * If the destination data are floating point neither scaling nor rounding
0782: * is performed.
0783: * </li>
0784: * </ol></p>
0785: *
0786: * <p> If the destination <code>WritableRaster</code> is <code>null</code>,
0787: * a new <code>WritableRaster</code> will be created. The
0788: * <code>Raster</code>s are treated as having no alpha channel, i.e.,
0789: * all bands are color bands.
0790: *
0791: * <p> This method is provided for the convenience of extenders defining
0792: * a color space for which the conversion is defined with respect to
0793: * sRGB.
0794: *
0795: * <p><i> It should be noted that there is no official specification
0796: * of sRGB with respect to digital codes of depths other than 8 bits.
0797: * The present implementation for other bit depths is provided as an
0798: * extrapolation of the 8-bit sRGB standard and its use should be
0799: * recognized as such. The extrapolation is implemented by replacing
0800: * the white digital count (<code>WDC</code>) and black digital count
0801: * (<code>KDC</code>) in the 8-bit sRGB specification with values
0802: * corresponding to the extrema of the data type in question when
0803: * treated as unsigned. For all data types <code>KDC</code> is zero
0804: * and <code>WDC</code> is specified by the component size
0805: * parameters.</i></p>
0806: *
0807: * @param src the source <code>Raster</code> to be converted.
0808: * @param srcComponentSize array that specifies the number of significant
0809: * bits per source color component; ignored for floating point data.
0810: * If <code>null</code> defaults to the value returned by
0811: * <code>src.getSampleModel().getSampleSize()</code>.
0812: * @param dest the destination <code>WritableRaster</code>,
0813: * or <code>null</code>.
0814: * @param destComponentSize array that specifies the number of significant
0815: * bits per destination color component; ignored for floating point
0816: * data. If <code>null</code>, defaults to the value returned by
0817: * <code>dest.getSampleModel().getSampleSize()</code>, or the sample
0818: * size of the newly created destination WritableRaster if dest is
0819: * null.
0820: * @return <code>dest</code> color converted from <code>src</code>
0821: * or a new, <code>WritableRaster</code> containing the converted
0822: * pixels if <code>dest</code> is <code>null</code>.
0823: * @exception IllegalArgumentException if <code>src</code> is
0824: * <code>null</code>, the number of source or destination
0825: * bands is not 3, or either component size array
0826: * is non-null and has length not equal to 3.
0827: */
0828: public static WritableRaster RGBToCIEXYZ(Raster src,
0829: int[] srcComponentSize, WritableRaster dest,
0830: int[] destComponentSize) {
0831:
0832: checkParameters(src, srcComponentSize, dest, destComponentSize);
0833:
0834: SampleModel srcSampleModel = src.getSampleModel();
0835:
0836: // if the srcComponentSize is not provided, use the sample sizes
0837: // from the source's sample model
0838: if (srcComponentSize == null)
0839: srcComponentSize = srcSampleModel.getSampleSize();
0840:
0841: // if the destination raster is not provided, create a new one
0842: if (dest == null) {
0843: Point origin = new Point(src.getMinX(), src.getMinY());
0844: dest = RasterFactory.createWritableRaster(srcSampleModel,
0845: origin);
0846: }
0847:
0848: SampleModel dstSampleModel = dest.getSampleModel();
0849:
0850: //if the destComponentSize is not provided, use the sample sizes
0851: //from the destination's sample model
0852: if (destComponentSize == null)
0853: destComponentSize = dstSampleModel.getSampleSize();
0854:
0855: PixelAccessor srcAcc = new PixelAccessor(srcSampleModel, null);
0856: UnpackedImageData srcUid = srcAcc.getPixels(src, src
0857: .getBounds(), srcSampleModel.getDataType(), false);
0858:
0859: switch (srcSampleModel.getDataType()) {
0860:
0861: case DataBuffer.TYPE_BYTE:
0862: RGBToCIEXYZByte(srcUid, srcComponentSize, dest,
0863: destComponentSize);
0864: break;
0865: case DataBuffer.TYPE_USHORT:
0866: case DataBuffer.TYPE_SHORT:
0867: RGBToCIEXYZShort(srcUid, srcComponentSize, dest,
0868: destComponentSize);
0869: break;
0870: case DataBuffer.TYPE_INT:
0871: RGBToCIEXYZInt(srcUid, srcComponentSize, dest,
0872: destComponentSize);
0873: break;
0874: case DataBuffer.TYPE_FLOAT:
0875: RGBToCIEXYZFloat(srcUid, srcComponentSize, dest,
0876: destComponentSize);
0877: break;
0878: case DataBuffer.TYPE_DOUBLE:
0879: RGBToCIEXYZDouble(srcUid, srcComponentSize, dest,
0880: destComponentSize);
0881: break;
0882: }
0883:
0884: return dest;
0885: }
0886:
0887: static void RGB2XYZ(float[] RGB, float[] XYZ) {
0888: for (int i = 0; i < 3; i++) {
0889: if (RGB[i] < 0.040449936F)
0890: RGB[i] /= 12.92F;
0891: else
0892: RGB[i] = (float) (Math.pow((RGB[i] + 0.055) / 1.055,
0893: 2.4));
0894: }
0895:
0896: XYZ[0] = 0.45593763F * RGB[0] + 0.39533819F * RGB[1]
0897: + 0.19954964F * RGB[2];
0898: XYZ[1] = 0.23157515F * RGB[0] + 0.77905262F * RGB[1]
0899: + 0.07864978F * RGB[2];
0900: XYZ[2] = 0.01593493F * RGB[0] + 0.09841772F * RGB[1]
0901: + 0.78488615F * RGB[2];
0902: }
0903:
0904: // convert a byte ratser from RGB to CIEXYZ
0905: private static void RGBToCIEXYZByte(UnpackedImageData src,
0906: int[] srcComponentSize, WritableRaster dest,
0907: int[] destComponentSize) {
0908: byte[] rBuf = src.getByteData(0);
0909: byte[] gBuf = src.getByteData(1);
0910: byte[] bBuf = src.getByteData(2);
0911:
0912: // used to left-shift the value to fill in all the 8-bits
0913: int normr = 8 - srcComponentSize[0];
0914: int normg = 8 - srcComponentSize[1];
0915: int normb = 8 - srcComponentSize[2];
0916:
0917: // the norms used to map the color value to the desired range
0918: double normx = 1.0, normy = normx, normz = normx;
0919:
0920: int dstType = dest.getSampleModel().getDataType();
0921: boolean isInt = (dstType < DataBuffer.TYPE_FLOAT);
0922:
0923: // for the integer type, redefine the norms and upper bounds
0924: // because rgb={1.0, 1.0, 1.0} is xyz={0.950456, 1.0, 1.088754},
0925: // so for normx, normz, they are specially treated
0926: if (isInt) {
0927: normx = ((1L << destComponentSize[0]) - 1) / maxXYZ;
0928: normy = ((1L << destComponentSize[1]) - 1) / maxXYZ;
0929: normz = ((1L << destComponentSize[2]) - 1) / maxXYZ;
0930: }
0931:
0932: int height = dest.getHeight();
0933: int width = dest.getWidth();
0934:
0935: double[] dstPixels = new double[3 * height * width];
0936:
0937: int rStart = src.bandOffsets[0];
0938: int gStart = src.bandOffsets[1];
0939: int bStart = src.bandOffsets[2];
0940: int srcPixelStride = src.pixelStride;
0941: int srcLineStride = src.lineStride;
0942:
0943: int dIndex = 0;
0944: for (int j = 0; j < height; j++, rStart += srcLineStride, gStart += srcLineStride, bStart += srcLineStride) {
0945: for (int i = 0, rIndex = rStart, gIndex = gStart, bIndex = bStart; i < width; i++, rIndex += srcPixelStride, gIndex += srcPixelStride, bIndex += srcPixelStride) {
0946: double R = LUT[(rBuf[rIndex] & 0xFF) << normr];
0947: double G = LUT[(gBuf[gIndex] & 0xFF) << normg];
0948: double B = LUT[(bBuf[bIndex] & 0xFF) << normb];
0949:
0950: if (isInt) {
0951: double X, Y, Z;
0952: dstPixels[dIndex++] = (0.45593763 * R + 0.39533819
0953: * G + 0.19954964 * B)
0954: * normx;
0955: dstPixels[dIndex++] = (0.23157515 * R + 0.77905262
0956: * G + 0.07864978 * B)
0957: * normy;
0958: dstPixels[dIndex++] = (0.01593493 * R + 0.09841772
0959: * G + 0.78488615 * B)
0960: * normz;
0961: } else {
0962: dstPixels[dIndex++] = 0.45593763 * R + 0.39533819
0963: * G + 0.19954964 * B;
0964: dstPixels[dIndex++] = 0.23157515 * R + 0.77905262
0965: * G + 0.07864978 * B;
0966: dstPixels[dIndex++] = 0.01593493 * R + 0.09841772
0967: * G + 0.78488615 * B;
0968: }
0969: }
0970: }
0971:
0972: // Because of 4738524: setPixels should round the provided double
0973: // value instead of casting
0974: // If it is fixed, then this piece of code can be removed.
0975: if (dstType < DataBuffer.TYPE_FLOAT)
0976: roundValues(dstPixels);
0977:
0978: convertToSigned(dstPixels, dstType);
0979: dest.setPixels(dest.getMinX(), dest.getMinY(), width, height,
0980: dstPixels);
0981: }
0982:
0983: // convert a short ratser from RGB to CIEXYZ
0984: private static void RGBToCIEXYZShort(UnpackedImageData src,
0985: int[] srcComponentSize, WritableRaster dest,
0986: int[] destComponentSize) {
0987: short[] rBuf = src.getShortData(0);
0988: short[] gBuf = src.getShortData(1);
0989: short[] bBuf = src.getShortData(2);
0990:
0991: // used to left-shift the input value to fill all the bits
0992: float normr = (1 << srcComponentSize[0]) - 1;
0993: float normg = (1 << srcComponentSize[1]) - 1;
0994: float normb = (1 << srcComponentSize[2]) - 1;
0995:
0996: // used to map the output to the desired range
0997: double normx = 1.0, normy = 1.0, normz = 1.0;
0998:
0999: int dstType = dest.getSampleModel().getDataType();
1000: boolean isInt = (dstType < DataBuffer.TYPE_FLOAT);
1001:
1002: // define the norms and upper bounds for the integer types
1003: // see the comments in RGBToCIEXYZByte
1004: if (isInt) {
1005: normx = ((1L << destComponentSize[0]) - 1) / maxXYZ;
1006: normy = ((1L << destComponentSize[1]) - 1) / maxXYZ;
1007: normz = ((1L << destComponentSize[2]) - 1) / maxXYZ;
1008: }
1009:
1010: int height = dest.getHeight();
1011: int width = dest.getWidth();
1012:
1013: double[] dstPixels = new double[3 * height * width];
1014:
1015: int rStart = src.bandOffsets[0];
1016: int gStart = src.bandOffsets[1];
1017: int bStart = src.bandOffsets[2];
1018: int srcPixelStride = src.pixelStride;
1019: int srcLineStride = src.lineStride;
1020:
1021: float[] XYZ = new float[3];
1022: float[] RGB = new float[3];
1023:
1024: int dIndex = 0;
1025: for (int j = 0; j < height; j++, rStart += srcLineStride, gStart += srcLineStride, bStart += srcLineStride) {
1026: for (int i = 0, rIndex = rStart, gIndex = gStart, bIndex = bStart; i < width; i++, rIndex += srcPixelStride, gIndex += srcPixelStride, bIndex += srcPixelStride) {
1027: RGB[0] = (rBuf[rIndex] & 0xFFFF) / normr;
1028: RGB[1] = (gBuf[gIndex] & 0xFFFF) / normg;
1029: RGB[2] = (bBuf[bIndex] & 0xFFFF) / normb;
1030:
1031: RGB2XYZ(RGB, XYZ);
1032:
1033: if (isInt) {
1034: dstPixels[dIndex++] = XYZ[0] * normx;
1035: dstPixels[dIndex++] = XYZ[1] * normy;
1036: dstPixels[dIndex++] = XYZ[2] * normz;
1037: } else {
1038: dstPixels[dIndex++] = XYZ[0];
1039: dstPixels[dIndex++] = XYZ[1];
1040: dstPixels[dIndex++] = XYZ[2];
1041: }
1042: }
1043: }
1044:
1045: // Because of 4738524: setPixels should round the provided double
1046: // value instead of casting
1047: // If it is fixed, then this piece of code can be removed.
1048: if (dstType < DataBuffer.TYPE_FLOAT)
1049: roundValues(dstPixels);
1050:
1051: convertToSigned(dstPixels, dstType);
1052: dest.setPixels(dest.getMinX(), dest.getMinY(), width, height,
1053: dstPixels);
1054: }
1055:
1056: // convert a int type ratser from RGB to CIEXYZ
1057: private static void RGBToCIEXYZInt(UnpackedImageData src,
1058: int[] srcComponentSize, WritableRaster dest,
1059: int[] destComponentSize) {
1060: int[] rBuf = src.getIntData(0);
1061: int[] gBuf = src.getIntData(1);
1062: int[] bBuf = src.getIntData(2);
1063:
1064: // used to left-shift the input to fill all the bits
1065: float normr = (1L << srcComponentSize[0]) - 1;
1066: float normg = (1L << srcComponentSize[1]) - 1;
1067: float normb = (1L << srcComponentSize[2]) - 1;
1068:
1069: // norms to map the output to the desired range
1070: double normx = 1.0, normy = 1.0, normz = 1.0;
1071:
1072: int dstType = dest.getSampleModel().getDataType();
1073: boolean isInt = (dstType < DataBuffer.TYPE_FLOAT);
1074:
1075: // define the norm and upper bounds for the integer output types
1076: // see also the comments in RGBToCIEXYZByte
1077: if (isInt) {
1078: normx = ((1L << destComponentSize[0]) - 1) / maxXYZ;
1079: normy = ((1L << destComponentSize[1]) - 1) / maxXYZ;
1080: normz = ((1L << destComponentSize[2]) - 1) / maxXYZ;
1081: }
1082:
1083: int height = dest.getHeight();
1084: int width = dest.getWidth();
1085:
1086: double[] dstPixels = new double[3 * height * width];
1087:
1088: int rStart = src.bandOffsets[0];
1089: int gStart = src.bandOffsets[1];
1090: int bStart = src.bandOffsets[2];
1091: int srcPixelStride = src.pixelStride;
1092: int srcLineStride = src.lineStride;
1093:
1094: float[] XYZ = new float[3];
1095: float[] RGB = new float[3];
1096:
1097: int dIndex = 0;
1098: for (int j = 0; j < height; j++, rStart += srcLineStride, gStart += srcLineStride, bStart += srcLineStride) {
1099: for (int i = 0, rIndex = rStart, gIndex = gStart, bIndex = bStart; i < width; i++, rIndex += srcPixelStride, gIndex += srcPixelStride, bIndex += srcPixelStride) {
1100: RGB[0] = (rBuf[rIndex] & 0xFFFFFFFFl) / normr;
1101: RGB[1] = (gBuf[gIndex] & 0xFFFFFFFFl) / normg;
1102: RGB[2] = (bBuf[bIndex] & 0xFFFFFFFFl) / normb;
1103:
1104: RGB2XYZ(RGB, XYZ);
1105:
1106: if (isInt) {
1107: dstPixels[dIndex++] = XYZ[0] * normx;
1108: dstPixels[dIndex++] = XYZ[1] * normx;
1109: dstPixels[dIndex++] = XYZ[2] * normx;
1110: } else {
1111: dstPixels[dIndex++] = XYZ[0];
1112: dstPixels[dIndex++] = XYZ[1];
1113: dstPixels[dIndex++] = XYZ[2];
1114: }
1115: }
1116: }
1117:
1118: // Because of 4738524: setPixels should round the provided double
1119: // value instead of casting
1120: // If it is fixed, then this piece of code can be removed.
1121: if (dstType < DataBuffer.TYPE_FLOAT)
1122: roundValues(dstPixels);
1123:
1124: convertToSigned(dstPixels, dstType);
1125: dest.setPixels(dest.getMinX(), dest.getMinY(), width, height,
1126: dstPixels);
1127: }
1128:
1129: // convert a float type ratser from RGB to CIEXYZ color space
1130: private static void RGBToCIEXYZFloat(UnpackedImageData src,
1131: int[] srcComponentSize, WritableRaster dest,
1132: int[] destComponentSize) {
1133: float[] rBuf = src.getFloatData(0);
1134: float[] gBuf = src.getFloatData(1);
1135: float[] bBuf = src.getFloatData(2);
1136:
1137: // norms to map the output value to the desired range
1138: double normx = 1.0, normy = 1.0, normz = 1.0;
1139:
1140: int dstType = dest.getSampleModel().getDataType();
1141: boolean isInt = (dstType < DataBuffer.TYPE_FLOAT);
1142:
1143: // define the norms and upper bounds for the integer types
1144: if (isInt) {
1145: normx = ((1L << destComponentSize[0]) - 1) / maxXYZ;
1146: normy = ((1L << destComponentSize[1]) - 1) / maxXYZ;
1147: normz = ((1L << destComponentSize[2]) - 1) / maxXYZ;
1148: }
1149:
1150: int height = dest.getHeight();
1151: int width = dest.getWidth();
1152:
1153: double[] dstPixels = new double[3 * height * width];
1154:
1155: int rStart = src.bandOffsets[0];
1156: int gStart = src.bandOffsets[1];
1157: int bStart = src.bandOffsets[2];
1158: int srcPixelStride = src.pixelStride;
1159: int srcLineStride = src.lineStride;
1160:
1161: float[] XYZ = new float[3];
1162: float[] RGB = new float[3];
1163:
1164: int dIndex = 0;
1165: for (int j = 0; j < height; j++, rStart += srcLineStride, gStart += srcLineStride, bStart += srcLineStride) {
1166: for (int i = 0, rIndex = rStart, gIndex = gStart, bIndex = bStart; i < width; i++, rIndex += srcPixelStride, gIndex += srcPixelStride, bIndex += srcPixelStride) {
1167: RGB[0] = rBuf[rIndex];
1168: RGB[1] = gBuf[gIndex];
1169: RGB[2] = bBuf[bIndex];
1170:
1171: RGB2XYZ(RGB, XYZ);
1172:
1173: if (isInt) {
1174: dstPixels[dIndex++] = XYZ[0] * normx;
1175: dstPixels[dIndex++] = XYZ[1] * normx;
1176: dstPixels[dIndex++] = XYZ[2] * normx;
1177: } else {
1178: dstPixels[dIndex++] = XYZ[0];
1179: dstPixels[dIndex++] = XYZ[1];
1180: dstPixels[dIndex++] = XYZ[2];
1181: }
1182: }
1183: }
1184:
1185: // Because of 4738524: setPixels should round the provided double
1186: // value instead of casting
1187: // If it is fixed, then this piece of code can be removed.
1188: if (dstType < DataBuffer.TYPE_FLOAT)
1189: roundValues(dstPixels);
1190:
1191: convertToSigned(dstPixels, dstType);
1192: dest.setPixels(dest.getMinX(), dest.getMinY(), width, height,
1193: dstPixels);
1194: }
1195:
1196: // convert a double type raster from RGB to CIEXYZ
1197: private static void RGBToCIEXYZDouble(UnpackedImageData src,
1198: int[] srcComponentSize, WritableRaster dest,
1199: int[] destComponentSize) {
1200: double[] rBuf = src.getDoubleData(0);
1201: double[] gBuf = src.getDoubleData(1);
1202: double[] bBuf = src.getDoubleData(2);
1203:
1204: // norms to map the output to the desired range
1205: double normx = 1.0, normy = 1.0, normz = 1.0;
1206:
1207: int dstType = dest.getSampleModel().getDataType();
1208: boolean isInt = (dstType < DataBuffer.TYPE_FLOAT);
1209:
1210: if (isInt) {
1211: normx = ((1L << destComponentSize[0]) - 1) / maxXYZ;
1212: normy = ((1L << destComponentSize[1]) - 1) / maxXYZ;
1213: normz = ((1L << destComponentSize[2]) - 1) / maxXYZ;
1214: }
1215:
1216: int height = dest.getHeight();
1217: int width = dest.getWidth();
1218:
1219: double[] dstPixels = new double[3 * height * width];
1220:
1221: int rStart = src.bandOffsets[0];
1222: int gStart = src.bandOffsets[1];
1223: int bStart = src.bandOffsets[2];
1224: int srcPixelStride = src.pixelStride;
1225: int srcLineStride = src.lineStride;
1226:
1227: float[] XYZ = new float[3];
1228: float[] RGB = new float[3];
1229:
1230: int dIndex = 0;
1231: for (int j = 0; j < height; j++, rStart += srcLineStride, gStart += srcLineStride, bStart += srcLineStride) {
1232: for (int i = 0, rIndex = rStart, gIndex = gStart, bIndex = bStart; i < width; i++, rIndex += srcPixelStride, gIndex += srcPixelStride, bIndex += srcPixelStride) {
1233: RGB[0] = (float) rBuf[rIndex];
1234: RGB[1] = (float) gBuf[gIndex];
1235: RGB[2] = (float) bBuf[bIndex];
1236:
1237: RGB2XYZ(RGB, XYZ);
1238:
1239: if (isInt) {
1240: dstPixels[dIndex++] = XYZ[0] * normx;
1241: dstPixels[dIndex++] = XYZ[1] * normx;
1242: dstPixels[dIndex++] = XYZ[2] * normx;
1243: } else {
1244: dstPixels[dIndex++] = XYZ[0];
1245: dstPixels[dIndex++] = XYZ[1];
1246: dstPixels[dIndex++] = XYZ[2];
1247: }
1248: }
1249: }
1250:
1251: // Because of 4738524: setPixels should round the provided double
1252: // value instead of casting
1253: // If it is fixed, then this piece of code can be removed.
1254: if (dstType < DataBuffer.TYPE_FLOAT)
1255: roundValues(dstPixels);
1256:
1257: convertToSigned(dstPixels, dstType);
1258: dest.setPixels(dest.getMinX(), dest.getMinY(), width, height,
1259: dstPixels);
1260: }
1261:
1262: /**
1263: * Constructs a <code>ColorSpaceJAI</code> object given the color space
1264: * type, the number of components, and an indicator of the preferred
1265: * intermediary or connection color space.
1266: *
1267: * @param type The color space type (<code>ColorSpace.TYPE_*</code>).
1268: * @param numComponents The number of color components.
1269: * @param isRGBPreferredIntermediary Whether sRGB (<code>true</code>)
1270: * or CIEXYZ (<code>false</code>) is the preferred connection
1271: * color space.
1272: *
1273: * @exception IllegalArgumentException if <code>numComponents</code>
1274: * is non-positive.
1275: */
1276: protected ColorSpaceJAI(int type, int numComponents,
1277: boolean isRGBPreferredIntermediary) {
1278: super (type, numComponents);
1279: this .isRGBPreferredIntermediary = isRGBPreferredIntermediary;
1280: }
1281:
1282: /**
1283: * Whether sRGB is the preferred intermediary color space when converting
1284: * to another color space which is neither sRGB nor CIEXYZ. This
1285: * serves to indicate the more efficient conversion pathway.
1286: *
1287: * @return <code>true</code> if sRGB is preferred, or <code>false</code>
1288: * if CIEXYZ is preferred.
1289: */
1290: public boolean isRGBPreferredIntermediary() {
1291: return this .isRGBPreferredIntermediary;
1292: }
1293:
1294: /**
1295: * Transforms the pixel data in the source <code>Raster</code> from
1296: * CIEXYZ values with respect to the CIE D50 white point to the color
1297: * space represented by this class. If the destination
1298: * <code>WritableRaster</code> is <code>null</code>, a new
1299: * <code>WritableRaster</code> will be created. The
1300: * <code>Raster</code>s are treated as having no alpha channel, i.e.,
1301: * all bands are color bands.
1302: *
1303: * <p> If required by the underlying transformation, integral data will
1304: * be normalized according to the number of bits of the respective
1305: * component; floating point data should be between 0.0 and
1306: * 1.0 + (32767.0 / 32768.0).
1307: * All integral data are assumed to be unsigned; signed data should be
1308: * shifted by the caller before invoking this method.
1309: *
1310: * @param src the source <code>Raster</code> to be converted.
1311: * @param srcComponentSize array that specifies the number of significant
1312: * bits per source color component; ignored for floating point data.
1313: * If <code>null</code> defaults to the value returned by
1314: * <code>src.getSampleModel().getSampleSize()</code>.
1315: * @param dest the destination <code>WritableRaster</code>,
1316: * or <code>null</code>.
1317: * @param destComponentSize array that specifies the number of significant
1318: * bits per destination color component; ignored for floating point
1319: * data. If <code>null</code>, defaults to the value returned by
1320: * <code>dest.getSampleModel().getSampleSize()</code>, or the sample
1321: * size of the newly created destination WritableRaster if dest is
1322: * null.
1323: * @return <code>dest</code> color converted from <code>src</code>
1324: * or a new, <code>WritableRaster</code> containing the converted
1325: * pixels if <code>dest</code> is <code>null</code>.
1326: * @exception IllegalArgumentException if <code>src</code> is
1327: * <code>null</code>, the number of source or destination
1328: * bands does not equal the number of components of the
1329: * respective color space, or either component size array
1330: * is non-null and has length not equal to the number of
1331: * bands in the respective <code>Raster</code>.
1332: *
1333: */
1334: public abstract WritableRaster fromCIEXYZ(Raster src,
1335: int[] srcComponentSize, WritableRaster dest,
1336: int[] destComponentSize);
1337:
1338: /**
1339: * Transforms the pixel data in the source <code>Raster</code> from
1340: * sRGB to the color space represented by this class.
1341: * If the destination <code>WritableRaster</code> is <code>null</code>,
1342: * a new <code>WritableRaster</code> will be created. The
1343: * <code>Raster</code>s are treated as having no alpha channel, i.e.,
1344: * all bands are color bands.
1345: *
1346: * <p> If required by the underlying transformation, integral data will
1347: * be normalized according to the number of bits of the respective
1348: * component; floating point data should be between 0.0 and 1.0.
1349: * All integral data are assumed to be unsigned; signed data should be
1350: * shifted by the caller before invoking this method.
1351: *
1352: * @param src the source <code>Raster</code> to be converted.
1353: * @param srcComponentSize array that specifies the number of significant
1354: * bits per source color component; ignored for floating point data.
1355: * If <code>null</code> defaults to the value returned by
1356: * <code>src.getSampleModel().getSampleSize()</code>.
1357: * @param dest the destination <code>WritableRaster</code>,
1358: * or <code>null</code>.
1359: * @param destComponentSize array that specifies the number of significant
1360: * bits per destination color component; ignored for floating point
1361: * data. If <code>null</code>, defaults to the value returned by
1362: * <code>dest.getSampleModel().getSampleSize()</code>, or the sample
1363: * size of the newly created destination WritableRaster if dest is
1364: * null.
1365: * @return <code>dest</code> color converted from <code>src</code>
1366: * or a new, <code>WritableRaster</code> containing the converted
1367: * pixels if <code>dest</code> is <code>null</code>.
1368: * @exception IllegalArgumentException if <code>src</code> is
1369: * <code>null</code>, the number of source or destination
1370: * bands does not equal the number of components of the
1371: * respective color space, or either component size array
1372: * is non-null and has length not equal to the number of
1373: * bands in the respective <code>Raster</code>.
1374: *
1375: */
1376: public abstract WritableRaster fromRGB(Raster src,
1377: int[] srcComponentSize, WritableRaster dest,
1378: int[] destComponentSize);
1379:
1380: /**
1381: * Transforms the pixel data in the source <code>Raster</code> from
1382: * the color space represented by this class to CIEXYZ values with
1383: * respect to the CIE D50 white point. If the destination
1384: * <code>WritableRaster</code> is <code>null</code>, a new
1385: * <code>WritableRaster</code> will be created. The
1386: * <code>Raster</code>s are treated as having no alpha channel, i.e.,
1387: * all bands are color bands.
1388: *
1389: * <p> If required by the underlying transformation, integral data will
1390: * be normalized according to the number of bits of the respective
1391: * component; floating point data should be between 0.0 and 1.0.
1392: * All integral data are assumed to be unsigned; signed data should be
1393: * shifted by the caller before invoking this method.
1394: *
1395: * @param src the source <code>Raster</code> to be converted.
1396: * @param srcComponentSize array that specifies the number of significant
1397: * bits per source color component; ignored for floating point data.
1398: * If <code>null</code> defaults to the value returned by
1399: * <code>src.getSampleModel().getSampleSize()</code>.
1400: * @param dest the destination <code>WritableRaster</code>,
1401: * or <code>null</code>.
1402: * @param destComponentSize array that specifies the number of significant
1403: * bits per destination color component; ignored for floating point
1404: * data. If <code>null</code>, defaults to the value returned by
1405: * <code>dest.getSampleModel().getSampleSize()</code>, or the sample
1406: * size of the newly created destination WritableRaster if dest is
1407: * null.
1408: * @return <code>dest</code> color converted from <code>src</code>
1409: * or a new, <code>WritableRaster</code> containing the converted
1410: * pixels if <code>dest</code> is <code>null</code>.
1411: * @exception IllegalArgumentException if <code>src</code> is
1412: * <code>null</code>, the number of source or destination
1413: * bands does not equal the number of components of the
1414: * respective color space, or either component size array
1415: * is non-null and has length not equal to the number of
1416: * bands in the respective <code>Raster</code>.
1417: *
1418: */
1419: public abstract WritableRaster toCIEXYZ(Raster src,
1420: int[] srcComponentSize, WritableRaster dest,
1421: int[] destComponentSize);
1422:
1423: /**
1424: * Transforms the pixel data in the source <code>Raster</code> from
1425: * the color space represented by this class to sRGB.
1426: * If the destination <code>WritableRaster</code> is <code>null</code>,
1427: * a new <code>WritableRaster</code> will be created. The
1428: * <code>Raster</code>s are treated as having no alpha channel, i.e.,
1429: * all bands are color bands.
1430: *
1431: * <p> If required by the underlying transformation, integral data will
1432: * be normalized according to the number of bits of the respective
1433: * component; floating point data should be between 0.0 and 1.0.
1434: * All integral data are assumed to be unsigned; signed data should be
1435: * shifted by the caller before invoking this method.
1436: *
1437: * @param src the source <code>Raster</code> to be converted.
1438: * @param srcComponentSize array that specifies the number of significant
1439: * bits per source color component; ignored for floating point data.
1440: * If <code>null</code> defaults to the value returned by
1441: * <code>src.getSampleModel().getSampleSize()</code>.
1442: * @param dest the destination <code>WritableRaster</code>,
1443: * or <code>null</code>.
1444: * @param destComponentSize array that specifies the number of significant
1445: * bits per destination color component; ignored for floating point
1446: * data. If <code>null</code>, defaults to the value returned by
1447: * <code>dest.getSampleModel().getSampleSize()</code>, or the sample
1448: * size of the newly created destination WritableRaster if dest is
1449: * null.
1450: * @return <code>dest</code> color converted from <code>src</code>
1451: * or a new, <code>WritableRaster</code> containing the converted
1452: * pixels if <code>dest</code> is <code>null</code>.
1453: * @exception IllegalArgumentException if <code>src</code> is
1454: * <code>null</code>, the number of source or destination
1455: * bands does not equal the number of components of the
1456: * respective color space, or either component size array
1457: * is non-null and has length not equal to the number of
1458: * bands in the respective <code>Raster</code>.
1459: *
1460: */
1461: public abstract WritableRaster toRGB(Raster src,
1462: int[] srcComponentSize, WritableRaster dest,
1463: int[] destComponentSize);
1464: }
|