jdk/src/share/classes/java/awt/image/ColorConvertOp.java
changeset 539 7952521a4ad3
child 715 f16baef3a20e
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/java/awt/image/ColorConvertOp.java	Thu Apr 10 16:28:45 2008 -0700
@@ -0,0 +1,1109 @@
+/*
+ * Portions Copyright 1997-2007 Sun Microsystems, Inc.  All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+/**********************************************************************
+ **********************************************************************
+ **********************************************************************
+ *** COPYRIGHT (c) Eastman Kodak Company, 1997                      ***
+ *** As  an unpublished  work pursuant to Title 17 of the United    ***
+ *** States Code.  All rights reserved.                             ***
+ **********************************************************************
+ **********************************************************************
+ **********************************************************************/
+
+package java.awt.image;
+
+import java.awt.Point;
+import java.awt.Graphics2D;
+import java.awt.color.*;
+import sun.java2d.cmm.ColorTransform;
+import sun.java2d.cmm.CMSManager;
+import sun.java2d.cmm.ProfileDeferralMgr;
+import sun.java2d.cmm.PCMM;
+import java.awt.geom.Rectangle2D;
+import java.awt.geom.Point2D;
+import java.awt.RenderingHints;
+
+/**
+ * This class performs a pixel-by-pixel color conversion of the data in
+ * the source image.  The resulting color values are scaled to the precision
+ * of the destination image.  Color conversion can be specified
+ * via an array of ColorSpace objects or an array of ICC_Profile objects.
+ * <p>
+ * If the source is a BufferedImage with premultiplied alpha, the
+ * color components are divided by the alpha component before color conversion.
+ * If the destination is a BufferedImage with premultiplied alpha, the
+ * color components are multiplied by the alpha component after conversion.
+ * Rasters are treated as having no alpha channel, i.e. all bands are
+ * color bands.
+ * <p>
+ * If a RenderingHints object is specified in the constructor, the
+ * color rendering hint and the dithering hint may be used to control
+ * color conversion.
+ * <p>
+ * Note that Source and Destination may be the same object.
+ * <p>
+ * @see java.awt.RenderingHints#KEY_COLOR_RENDERING
+ * @see java.awt.RenderingHints#KEY_DITHERING
+ */
+public class ColorConvertOp implements BufferedImageOp, RasterOp {
+    ICC_Profile[]    profileList;
+    ColorSpace[]     CSList;
+    ColorTransform    thisTransform, thisRasterTransform;
+    ICC_Profile      thisSrcProfile, thisDestProfile;
+    RenderingHints   hints;
+    boolean          gotProfiles;
+    float[]          srcMinVals, srcMaxVals, dstMinVals, dstMaxVals;
+
+    /* the class initializer */
+    static {
+        if (ProfileDeferralMgr.deferring) {
+            ProfileDeferralMgr.activateProfiles();
+        }
+    }
+
+    /**
+     * Constructs a new ColorConvertOp which will convert
+     * from a source color space to a destination color space.
+     * The RenderingHints argument may be null.
+     * This Op can be used only with BufferedImages, and will convert
+     * directly from the ColorSpace of the source image to that of the
+     * destination.  The destination argument of the filter method
+     * cannot be specified as null.
+     * @param hints the <code>RenderingHints</code> object used to control
+     *        the color conversion, or <code>null</code>
+     */
+    public ColorConvertOp (RenderingHints hints)
+    {
+        profileList = new ICC_Profile [0];    /* 0 length list */
+        this.hints  = hints;
+    }
+
+    /**
+     * Constructs a new ColorConvertOp from a ColorSpace object.
+     * The RenderingHints argument may be null.  This
+     * Op can be used only with BufferedImages, and is primarily useful
+     * when the {@link #filter(BufferedImage, BufferedImage) filter}
+     * method is invoked with a destination argument of null.
+     * In that case, the ColorSpace defines the destination color space
+     * for the destination created by the filter method.  Otherwise, the
+     * ColorSpace defines an intermediate space to which the source is
+     * converted before being converted to the destination space.
+     * @param cspace defines the destination <code>ColorSpace</code> or an
+     *        intermediate <code>ColorSpace</code>
+     * @param hints the <code>RenderingHints</code> object used to control
+     *        the color conversion, or <code>null</code>
+     * @throws NullPointerException if cspace is null
+     */
+    public ColorConvertOp (ColorSpace cspace, RenderingHints hints)
+    {
+        if (cspace == null) {
+            throw new NullPointerException("ColorSpace cannot be null");
+        }
+        if (cspace instanceof ICC_ColorSpace) {
+            profileList = new ICC_Profile [1];    /* 1 profile in the list */
+
+            profileList [0] = ((ICC_ColorSpace) cspace).getProfile();
+        }
+        else {
+            CSList = new ColorSpace[1]; /* non-ICC case: 1 ColorSpace in list */
+            CSList[0] = cspace;
+        }
+        this.hints  = hints;
+    }
+
+
+    /**
+     * Constructs a new ColorConvertOp from two ColorSpace objects.
+     * The RenderingHints argument may be null.
+     * This Op is primarily useful for calling the filter method on
+     * Rasters, in which case the two ColorSpaces define the operation
+     * to be performed on the Rasters.  In that case, the number of bands
+     * in the source Raster must match the number of components in
+     * srcCspace, and the number of bands in the destination Raster
+     * must match the number of components in dstCspace.  For BufferedImages,
+     * the two ColorSpaces define intermediate spaces through which the
+     * source is converted before being converted to the destination space.
+     * @param srcCspace the source <code>ColorSpace</code>
+     * @param dstCspace the destination <code>ColorSpace</code>
+     * @param hints the <code>RenderingHints</code> object used to control
+     *        the color conversion, or <code>null</code>
+     * @throws NullPointerException if either srcCspace or dstCspace is null
+     */
+    public ColorConvertOp(ColorSpace srcCspace, ColorSpace dstCspace,
+                           RenderingHints hints)
+    {
+        if ((srcCspace == null) || (dstCspace == null)) {
+            throw new NullPointerException("ColorSpaces cannot be null");
+        }
+        if ((srcCspace instanceof ICC_ColorSpace) &&
+            (dstCspace instanceof ICC_ColorSpace)) {
+            profileList = new ICC_Profile [2];    /* 2 profiles in the list */
+
+            profileList [0] = ((ICC_ColorSpace) srcCspace).getProfile();
+            profileList [1] = ((ICC_ColorSpace) dstCspace).getProfile();
+
+            getMinMaxValsFromColorSpaces(srcCspace, dstCspace);
+        } else {
+            /* non-ICC case: 2 ColorSpaces in list */
+            CSList = new ColorSpace[2];
+            CSList[0] = srcCspace;
+            CSList[1] = dstCspace;
+        }
+        this.hints  = hints;
+    }
+
+
+     /**
+     * Constructs a new ColorConvertOp from an array of ICC_Profiles.
+     * The RenderingHints argument may be null.
+     * The sequence of profiles may include profiles that represent color
+     * spaces, profiles that represent effects, etc.  If the whole sequence
+     * does not represent a well-defined color conversion, an exception is
+     * thrown.
+     * <p>For BufferedImages, if the ColorSpace
+     * of the source BufferedImage does not match the requirements of the
+     * first profile in the array,
+     * the first conversion is to an appropriate ColorSpace.
+     * If the requirements of the last profile in the array are not met
+     * by the ColorSpace of the destination BufferedImage,
+     * the last conversion is to the destination's ColorSpace.
+     * <p>For Rasters, the number of bands in the source Raster must match
+     * the requirements of the first profile in the array, and the
+     * number of bands in the destination Raster must match the requirements
+     * of the last profile in the array.  The array must have at least two
+     * elements or calling the filter method for Rasters will throw an
+     * IllegalArgumentException.
+     * @param profiles the array of <code>ICC_Profile</code> objects
+     * @param hints the <code>RenderingHints</code> object used to control
+     *        the color conversion, or <code>null</code>
+     * @exception IllegalArgumentException when the profile sequence does not
+     *             specify a well-defined color conversion
+     * @exception NullPointerException if profiles is null
+     */
+    public ColorConvertOp (ICC_Profile[] profiles, RenderingHints hints)
+    {
+        if (profiles == null) {
+            throw new NullPointerException("Profiles cannot be null");
+        }
+        gotProfiles = true;
+        profileList = new ICC_Profile[profiles.length];
+        for (int i1 = 0; i1 < profiles.length; i1++) {
+            profileList[i1] = profiles[i1];
+        }
+        this.hints  = hints;
+    }
+
+
+    /**
+     * Returns the array of ICC_Profiles used to construct this ColorConvertOp.
+     * Returns null if the ColorConvertOp was not constructed from such an
+     * array.
+     * @return the array of <code>ICC_Profile</code> objects of this
+     *         <code>ColorConvertOp</code>, or <code>null</code> if this
+     *         <code>ColorConvertOp</code> was not constructed with an
+     *         array of <code>ICC_Profile</code> objects.
+     */
+    public final ICC_Profile[] getICC_Profiles() {
+        if (gotProfiles) {
+            ICC_Profile[] profiles = new ICC_Profile[profileList.length];
+            for (int i1 = 0; i1 < profileList.length; i1++) {
+                profiles[i1] = profileList[i1];
+            }
+            return profiles;
+        }
+        return null;
+    }
+
+    /**
+     * ColorConverts the source BufferedImage.
+     * If the destination image is null,
+     * a BufferedImage will be created with an appropriate ColorModel.
+     * @param src the source <code>BufferedImage</code> to be converted
+     * @param dest the destination <code>BufferedImage</code>,
+     *        or <code>null</code>
+     * @return <code>dest</code> color converted from <code>src</code>
+     *         or a new, converted <code>BufferedImage</code>
+     *         if <code>dest</code> is <code>null</code>
+     * @exception IllegalArgumentException if dest is null and this op was
+     *             constructed using the constructor which takes only a
+     *             RenderingHints argument, since the operation is ill defined.
+     */
+    public final BufferedImage filter(BufferedImage src, BufferedImage dest) {
+        ColorSpace srcColorSpace, destColorSpace;
+        BufferedImage savdest = null;
+
+        if (src.getColorModel() instanceof IndexColorModel) {
+            IndexColorModel icm = (IndexColorModel) src.getColorModel();
+            src = icm.convertToIntDiscrete(src.getRaster(), true);
+        }
+        srcColorSpace = src.getColorModel().getColorSpace();
+        if (dest != null) {
+            if (dest.getColorModel() instanceof IndexColorModel) {
+                savdest = dest;
+                dest = null;
+                destColorSpace = null;
+            } else {
+                destColorSpace = dest.getColorModel().getColorSpace();
+            }
+        } else {
+            destColorSpace = null;
+        }
+
+        if ((CSList != null) ||
+            (!(srcColorSpace instanceof ICC_ColorSpace)) ||
+            ((dest != null) &&
+             (!(destColorSpace instanceof ICC_ColorSpace)))) {
+            /* non-ICC case */
+            dest = nonICCBIFilter(src, srcColorSpace, dest, destColorSpace);
+        } else {
+            dest = ICCBIFilter(src, srcColorSpace, dest, destColorSpace);
+        }
+
+        if (savdest != null) {
+            Graphics2D big = savdest.createGraphics();
+            try {
+                big.drawImage(dest, 0, 0, null);
+            } finally {
+                big.dispose();
+            }
+            return savdest;
+        } else {
+            return dest;
+        }
+    }
+
+    private final BufferedImage ICCBIFilter(BufferedImage src,
+                                            ColorSpace srcColorSpace,
+                                            BufferedImage dest,
+                                            ColorSpace destColorSpace) {
+    int              nProfiles = profileList.length;
+    ICC_Profile      srcProfile = null, destProfile = null;
+
+        srcProfile = ((ICC_ColorSpace) srcColorSpace).getProfile();
+
+        if (dest == null) {        /* last profile in the list defines
+                                      the output color space */
+            if (nProfiles == 0) {
+                throw new IllegalArgumentException(
+                    "Destination ColorSpace is undefined");
+            }
+            destProfile = profileList [nProfiles - 1];
+            dest = createCompatibleDestImage(src, null);
+        }
+        else {
+            if (src.getHeight() != dest.getHeight() ||
+                src.getWidth() != dest.getWidth()) {
+                throw new IllegalArgumentException(
+                    "Width or height of BufferedImages do not match");
+            }
+            destProfile = ((ICC_ColorSpace) destColorSpace).getProfile();
+        }
+
+        /* Checking if all profiles in the transform sequence are the same.
+         * If so, performing just copying the data.
+         */
+        if (srcProfile == destProfile) {
+            boolean noTrans = true;
+            for (int i = 0; i < nProfiles; i++) {
+                if (srcProfile != profileList[i]) {
+                    noTrans = false;
+                    break;
+                }
+            }
+            if (noTrans) {
+                Graphics2D g = dest.createGraphics();
+                try {
+                    g.drawImage(src, 0, 0, null);
+                } finally {
+                    g.dispose();
+                }
+
+                return dest;
+            }
+        }
+
+        /* make a new transform if needed */
+        if ((thisTransform == null) || (thisSrcProfile != srcProfile) ||
+            (thisDestProfile != destProfile) ) {
+            updateBITransform(srcProfile, destProfile);
+        }
+
+        /* color convert the image */
+        thisTransform.colorConvert(src, dest);
+
+        return dest;
+    }
+
+    private void updateBITransform(ICC_Profile srcProfile,
+                                   ICC_Profile destProfile) {
+        ICC_Profile[]    theProfiles;
+        int              i1, nProfiles, nTransforms, whichTrans, renderState;
+        ColorTransform[]  theTransforms;
+        boolean          useSrc = false, useDest = false;
+
+        nProfiles = profileList.length;
+        nTransforms = nProfiles;
+        if ((nProfiles == 0) || (srcProfile != profileList[0])) {
+            nTransforms += 1;
+            useSrc = true;
+        }
+        if ((nProfiles == 0) || (destProfile != profileList[nProfiles - 1]) ||
+            (nTransforms < 2)) {
+            nTransforms += 1;
+            useDest = true;
+        }
+
+        /* make the profile list */
+        theProfiles = new ICC_Profile[nTransforms]; /* the list of profiles
+                                                       for this Op */
+
+        int idx = 0;
+        if (useSrc) {
+            /* insert source as first profile */
+            theProfiles[idx++] = srcProfile;
+        }
+
+        for (i1 = 0; i1 < nProfiles; i1++) {
+                                   /* insert profiles defined in this Op */
+            theProfiles[idx++] = profileList [i1];
+        }
+
+        if (useDest) {
+            /* insert dest as last profile */
+            theProfiles[idx] = destProfile;
+        }
+
+        /* make the transform list */
+        theTransforms = new ColorTransform [nTransforms];
+
+        /* initialize transform get loop */
+        if (theProfiles[0].getProfileClass() == ICC_Profile.CLASS_OUTPUT) {
+                                        /* if first profile is a printer
+                                           render as colorimetric */
+            renderState = ICC_Profile.icRelativeColorimetric;
+        }
+        else {
+            renderState = ICC_Profile.icPerceptual; /* render any other
+                                                       class perceptually */
+        }
+
+        whichTrans = ColorTransform.In;
+
+        PCMM mdl = CMSManager.getModule();
+
+        /* get the transforms from each profile */
+        for (i1 = 0; i1 < nTransforms; i1++) {
+            if (i1 == nTransforms -1) {         /* last profile? */
+                whichTrans = ColorTransform.Out; /* get output transform */
+            }
+            else {      /* check for abstract profile */
+                if ((whichTrans == ColorTransform.Simulation) &&
+                    (theProfiles[i1].getProfileClass () ==
+                     ICC_Profile.CLASS_ABSTRACT)) {
+                renderState = ICC_Profile.icPerceptual;
+                    whichTrans = ColorTransform.In;
+                }
+            }
+
+            theTransforms[i1] = mdl.createTransform (
+                theProfiles[i1], renderState, whichTrans);
+
+            /* get this profile's rendering intent to select transform
+               from next profile */
+            renderState = getRenderingIntent(theProfiles[i1]);
+
+            /* "middle" profiles use simulation transform */
+            whichTrans = ColorTransform.Simulation;
+        }
+
+        /* make the net transform */
+        thisTransform = mdl.createTransform(theTransforms);
+
+        /* update corresponding source and dest profiles */
+        thisSrcProfile = srcProfile;
+        thisDestProfile = destProfile;
+    }
+
+    /**
+     * ColorConverts the image data in the source Raster.
+     * If the destination Raster is null, a new Raster will be created.
+     * The number of bands in the source and destination Rasters must
+     * meet the requirements explained above.  The constructor used to
+     * create this ColorConvertOp must have provided enough information
+     * to define both source and destination color spaces.  See above.
+     * Otherwise, an exception is thrown.
+     * @param src the source <code>Raster</code> to be converted
+     * @param dest the destination <code>WritableRaster</code>,
+     *        or <code>null</code>
+     * @return <code>dest</code> color converted from <code>src</code>
+     *         or a new, converted <code>WritableRaster</code>
+     *         if <code>dest</code> is <code>null</code>
+     * @exception IllegalArgumentException if the number of source or
+     *             destination bands is incorrect, the source or destination
+     *             color spaces are undefined, or this op was constructed
+     *             with one of the constructors that applies only to
+     *             operations on BufferedImages.
+     */
+    public final WritableRaster filter (Raster src, WritableRaster dest)  {
+
+        if (CSList != null) {
+            /* non-ICC case */
+            return nonICCRasterFilter(src, dest);
+        }
+        int nProfiles = profileList.length;
+        if (nProfiles < 2) {
+            throw new IllegalArgumentException(
+                "Source or Destination ColorSpace is undefined");
+        }
+        if (src.getNumBands() != profileList[0].getNumComponents()) {
+            throw new IllegalArgumentException(
+                "Numbers of source Raster bands and source color space " +
+                "components do not match");
+        }
+        if (dest == null) {
+            dest = createCompatibleDestRaster(src);
+        }
+        else {
+            if (src.getHeight() != dest.getHeight() ||
+                src.getWidth() != dest.getWidth()) {
+                throw new IllegalArgumentException(
+                    "Width or height of Rasters do not match");
+            }
+            if (dest.getNumBands() !=
+                profileList[nProfiles-1].getNumComponents()) {
+                throw new IllegalArgumentException(
+                    "Numbers of destination Raster bands and destination " +
+                    "color space components do not match");
+            }
+        }
+
+        /* make a new transform if needed */
+        if (thisRasterTransform == null) {
+            int              i1, whichTrans, renderState;
+            ColorTransform[]  theTransforms;
+
+            /* make the transform list */
+            theTransforms = new ColorTransform [nProfiles];
+
+            /* initialize transform get loop */
+            if (profileList[0].getProfileClass() == ICC_Profile.CLASS_OUTPUT) {
+                                            /* if first profile is a printer
+                                               render as colorimetric */
+                renderState = ICC_Profile.icRelativeColorimetric;
+            }
+            else {
+                renderState = ICC_Profile.icPerceptual; /* render any other
+                                                           class perceptually */
+            }
+
+            whichTrans = ColorTransform.In;
+
+            PCMM mdl = CMSManager.getModule();
+
+            /* get the transforms from each profile */
+            for (i1 = 0; i1 < nProfiles; i1++) {
+                if (i1 == nProfiles -1) {         /* last profile? */
+                    whichTrans = ColorTransform.Out; /* get output transform */
+                }
+                else {  /* check for abstract profile */
+                    if ((whichTrans == ColorTransform.Simulation) &&
+                        (profileList[i1].getProfileClass () ==
+                         ICC_Profile.CLASS_ABSTRACT)) {
+                        renderState = ICC_Profile.icPerceptual;
+                        whichTrans = ColorTransform.In;
+                    }
+                }
+
+                theTransforms[i1] = mdl.createTransform (
+                    profileList[i1], renderState, whichTrans);
+
+                /* get this profile's rendering intent to select transform
+                   from next profile */
+                renderState = getRenderingIntent(profileList[i1]);
+
+                /* "middle" profiles use simulation transform */
+                whichTrans = ColorTransform.Simulation;
+            }
+
+            /* make the net transform */
+            thisRasterTransform = mdl.createTransform(theTransforms);
+        }
+
+        int srcTransferType = src.getTransferType();
+        int dstTransferType = dest.getTransferType();
+        if ((srcTransferType == DataBuffer.TYPE_FLOAT) ||
+            (srcTransferType == DataBuffer.TYPE_DOUBLE) ||
+            (dstTransferType == DataBuffer.TYPE_FLOAT) ||
+            (dstTransferType == DataBuffer.TYPE_DOUBLE)) {
+            if (srcMinVals == null) {
+                getMinMaxValsFromProfiles(profileList[0],
+                                          profileList[nProfiles-1]);
+            }
+            /* color convert the raster */
+            thisRasterTransform.colorConvert(src, dest,
+                                             srcMinVals, srcMaxVals,
+                                             dstMinVals, dstMaxVals);
+        } else {
+            /* color convert the raster */
+            thisRasterTransform.colorConvert(src, dest);
+        }
+
+
+        return dest;
+    }
+
+    /**
+     * Returns the bounding box of the destination, given this source.
+     * Note that this will be the same as the the bounding box of the
+     * source.
+     * @param src the source <code>BufferedImage</code>
+     * @return a <code>Rectangle2D</code> that is the bounding box
+     *         of the destination, given the specified <code>src</code>
+     */
+    public final Rectangle2D getBounds2D (BufferedImage src) {
+        return getBounds2D(src.getRaster());
+    }
+
+    /**
+     * Returns the bounding box of the destination, given this source.
+     * Note that this will be the same as the the bounding box of the
+     * source.
+     * @param src the source <code>Raster</code>
+     * @return a <code>Rectangle2D</code> that is the bounding box
+     *         of the destination, given the specified <code>src</code>
+     */
+    public final Rectangle2D getBounds2D (Raster src) {
+        /*        return new Rectangle (src.getXOffset(),
+                              src.getYOffset(),
+                              src.getWidth(), src.getHeight()); */
+        return src.getBounds();
+    }
+
+    /**
+     * Creates a zeroed destination image with the correct size and number of
+     * bands, given this source.
+     * @param src       Source image for the filter operation.
+     * @param destCM    ColorModel of the destination.  If null, an
+     *                  appropriate ColorModel will be used.
+     * @return a <code>BufferedImage</code> with the correct size and
+     * number of bands from the specified <code>src</code>.
+     * @throws IllegalArgumentException if <code>destCM</code> is
+     *         <code>null</code> and this <code>ColorConvertOp</code> was
+     *         created without any <code>ICC_Profile</code> or
+     *         <code>ColorSpace</code> defined for the destination
+     */
+    public BufferedImage createCompatibleDestImage (BufferedImage src,
+                                                    ColorModel destCM) {
+        ColorSpace cs = null;;
+        if (destCM == null) {
+            if (CSList == null) {
+                /* ICC case */
+                int nProfiles = profileList.length;
+                if (nProfiles == 0) {
+                    throw new IllegalArgumentException(
+                        "Destination ColorSpace is undefined");
+                }
+                ICC_Profile destProfile = profileList[nProfiles - 1];
+                cs = new ICC_ColorSpace(destProfile);
+            } else {
+                /* non-ICC case */
+                int nSpaces = CSList.length;
+                cs = CSList[nSpaces - 1];
+            }
+        }
+        return createCompatibleDestImage(src, destCM, cs);
+    }
+
+    private BufferedImage createCompatibleDestImage(BufferedImage src,
+                                                    ColorModel destCM,
+                                                    ColorSpace destCS) {
+        BufferedImage image;
+        if (destCM == null) {
+            ColorModel srcCM = src.getColorModel();
+            int nbands = destCS.getNumComponents();
+            boolean hasAlpha = srcCM.hasAlpha();
+            if (hasAlpha) {
+               nbands += 1;
+            }
+            int[] nbits = new int[nbands];
+            for (int i = 0; i < nbands; i++) {
+                nbits[i] = 8;
+            }
+            destCM = new ComponentColorModel(destCS, nbits, hasAlpha,
+                                             srcCM.isAlphaPremultiplied(),
+                                             srcCM.getTransparency(),
+                                             DataBuffer.TYPE_BYTE);
+        }
+        int w = src.getWidth();
+        int h = src.getHeight();
+        image = new BufferedImage(destCM,
+                                  destCM.createCompatibleWritableRaster(w, h),
+                                  destCM.isAlphaPremultiplied(), null);
+        return image;
+    }
+
+
+    /**
+     * Creates a zeroed destination Raster with the correct size and number of
+     * bands, given this source.
+     * @param src the specified <code>Raster</code>
+     * @return a <code>WritableRaster</code> with the correct size and number
+     *         of bands from the specified <code>src</code>
+     * @throws IllegalArgumentException if this <code>ColorConvertOp</code>
+     *         was created without sufficient information to define the
+     *         <code>dst</code> and <code>src</code> color spaces
+     */
+    public WritableRaster createCompatibleDestRaster (Raster src) {
+        int ncomponents;
+
+        if (CSList != null) {
+            /* non-ICC case */
+            if (CSList.length != 2) {
+                throw new IllegalArgumentException(
+                    "Destination ColorSpace is undefined");
+            }
+            ncomponents = CSList[1].getNumComponents();
+        } else {
+            /* ICC case */
+            int nProfiles = profileList.length;
+            if (nProfiles < 2) {
+                throw new IllegalArgumentException(
+                    "Destination ColorSpace is undefined");
+            }
+            ncomponents = profileList[nProfiles-1].getNumComponents();
+        }
+
+        WritableRaster dest =
+            Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE,
+                                  src.getWidth(),
+                                  src.getHeight(),
+                                  ncomponents,
+                                  new Point(src.getMinX(), src.getMinY()));
+        return dest;
+    }
+
+    /**
+     * Returns the location of the destination point given a
+     * point in the source.  If <code>dstPt</code> is non-null,
+     * it will be used to hold the return value.  Note that
+     * for this class, the destination point will be the same
+     * as the source point.
+     * @param srcPt the specified source <code>Point2D</code>
+     * @param dstPt the destination <code>Point2D</code>
+     * @return <code>dstPt</code> after setting its location to be
+     *         the same as <code>srcPt</code>
+     */
+    public final Point2D getPoint2D (Point2D srcPt, Point2D dstPt) {
+        if (dstPt == null) {
+            dstPt = new Point2D.Float();
+        }
+        dstPt.setLocation(srcPt.getX(), srcPt.getY());
+
+        return dstPt;
+    }
+
+
+    /**
+     * Returns the RenderingIntent from the specified ICC Profile.
+     */
+    private int getRenderingIntent (ICC_Profile profile) {
+        byte[] header = profile.getData(ICC_Profile.icSigHead);
+        int index = ICC_Profile.icHdrRenderingIntent;
+        return (((header[index]   & 0xff) << 24) |
+                ((header[index+1] & 0xff) << 16) |
+                ((header[index+2] & 0xff) <<  8) |
+                 (header[index+3] & 0xff));
+    }
+
+    /**
+     * Returns the rendering hints used by this op.
+     * @return the <code>RenderingHints</code> object of this
+     *         <code>ColorConvertOp</code>
+     */
+    public final RenderingHints getRenderingHints() {
+        return hints;
+    }
+
+    private final BufferedImage nonICCBIFilter(BufferedImage src,
+                                               ColorSpace srcColorSpace,
+                                               BufferedImage dst,
+                                               ColorSpace dstColorSpace) {
+
+        int w = src.getWidth();
+        int h = src.getHeight();
+        ICC_ColorSpace ciespace =
+            (ICC_ColorSpace) ColorSpace.getInstance(ColorSpace.CS_CIEXYZ);
+        if (dst == null) {
+            dst = createCompatibleDestImage(src, null);
+            dstColorSpace = dst.getColorModel().getColorSpace();
+        } else {
+            if ((h != dst.getHeight()) || (w != dst.getWidth())) {
+                throw new IllegalArgumentException(
+                    "Width or height of BufferedImages do not match");
+            }
+        }
+        Raster srcRas = src.getRaster();
+        WritableRaster dstRas = dst.getRaster();
+        ColorModel srcCM = src.getColorModel();
+        ColorModel dstCM = dst.getColorModel();
+        int srcNumComp = srcCM.getNumColorComponents();
+        int dstNumComp = dstCM.getNumColorComponents();
+        boolean dstHasAlpha = dstCM.hasAlpha();
+        boolean needSrcAlpha = srcCM.hasAlpha() && dstHasAlpha;
+        ColorSpace[] list;
+        if ((CSList == null) && (profileList.length != 0)) {
+            /* possible non-ICC src, some profiles, possible non-ICC dst */
+            boolean nonICCSrc, nonICCDst;
+            ICC_Profile srcProfile, dstProfile;
+            if (!(srcColorSpace instanceof ICC_ColorSpace)) {
+                nonICCSrc = true;
+                srcProfile = ciespace.getProfile();
+            } else {
+                nonICCSrc = false;
+                srcProfile = ((ICC_ColorSpace) srcColorSpace).getProfile();
+            }
+            if (!(dstColorSpace instanceof ICC_ColorSpace)) {
+                nonICCDst = true;
+                dstProfile = ciespace.getProfile();
+            } else {
+                nonICCDst = false;
+                dstProfile = ((ICC_ColorSpace) dstColorSpace).getProfile();
+            }
+            /* make a new transform if needed */
+            if ((thisTransform == null) || (thisSrcProfile != srcProfile) ||
+                (thisDestProfile != dstProfile) ) {
+                updateBITransform(srcProfile, dstProfile);
+            }
+            // process per scanline
+            float maxNum = 65535.0f; // use 16-bit precision in CMM
+            ColorSpace cs;
+            int iccSrcNumComp;
+            if (nonICCSrc) {
+                cs = ciespace;
+                iccSrcNumComp = 3;
+            } else {
+                cs = srcColorSpace;
+                iccSrcNumComp = srcNumComp;
+            }
+            float[] srcMinVal = new float[iccSrcNumComp];
+            float[] srcInvDiffMinMax = new float[iccSrcNumComp];
+            for (int i = 0; i < srcNumComp; i++) {
+                srcMinVal[i] = cs.getMinValue(i);
+                srcInvDiffMinMax[i] = maxNum / (cs.getMaxValue(i) - srcMinVal[i]);
+            }
+            int iccDstNumComp;
+            if (nonICCDst) {
+                cs = ciespace;
+                iccDstNumComp = 3;
+            } else {
+                cs = dstColorSpace;
+                iccDstNumComp = dstNumComp;
+            }
+            float[] dstMinVal = new float[iccDstNumComp];
+            float[] dstDiffMinMax = new float[iccDstNumComp];
+            for (int i = 0; i < dstNumComp; i++) {
+                dstMinVal[i] = cs.getMinValue(i);
+                dstDiffMinMax[i] = (cs.getMaxValue(i) - dstMinVal[i]) / maxNum;
+            }
+            float[] dstColor;
+            if (dstHasAlpha) {
+                int size = ((dstNumComp + 1) > 3) ? (dstNumComp + 1) : 3;
+                dstColor = new float[size];
+            } else {
+                int size = (dstNumComp  > 3) ? dstNumComp : 3;
+                dstColor = new float[size];
+            }
+            short[] srcLine = new short[w * iccSrcNumComp];
+            short[] dstLine = new short[w * iccDstNumComp];
+            Object pixel;
+            float[] color;
+            float[] alpha = null;
+            if (needSrcAlpha) {
+                alpha = new float[w];
+            }
+            int idx;
+            // process each scanline
+            for (int y = 0; y < h; y++) {
+                // convert src scanline
+                pixel = null;
+                color = null;
+                idx = 0;
+                for (int x = 0; x < w; x++) {
+                    pixel = srcRas.getDataElements(x, y, pixel);
+                    color = srcCM.getNormalizedComponents(pixel, color, 0);
+                    if (needSrcAlpha) {
+                        alpha[x] = color[srcNumComp];
+                    }
+                    if (nonICCSrc) {
+                        color = srcColorSpace.toCIEXYZ(color);
+                    }
+                    for (int i = 0; i < iccSrcNumComp; i++) {
+                        srcLine[idx++] = (short)
+                            ((color[i] - srcMinVal[i]) * srcInvDiffMinMax[i] +
+                             0.5f);
+                    }
+                }
+                // color convert srcLine to dstLine
+                thisTransform.colorConvert(srcLine, dstLine);
+                // convert dst scanline
+                pixel = null;
+                idx = 0;
+                for (int x = 0; x < w; x++) {
+                    for (int i = 0; i < iccDstNumComp; i++) {
+                        dstColor[i] = ((float) (dstLine[idx++] & 0xffff)) *
+                                      dstDiffMinMax[i] + dstMinVal[i];
+                    }
+                    if (nonICCDst) {
+                        color = srcColorSpace.fromCIEXYZ(dstColor);
+                        for (int i = 0; i < dstNumComp; i++) {
+                            dstColor[i] = color[i];
+                        }
+                    }
+                    if (needSrcAlpha) {
+                        dstColor[dstNumComp] = alpha[x];
+                    } else if (dstHasAlpha) {
+                        dstColor[dstNumComp] = 1.0f;
+                    }
+                    pixel = dstCM.getDataElements(dstColor, 0, pixel);
+                    dstRas.setDataElements(x, y, pixel);
+                }
+            }
+        } else {
+            /* possible non-ICC src, possible CSList, possible non-ICC dst */
+            // process per pixel
+            int numCS;
+            if (CSList == null) {
+                numCS = 0;
+            } else {
+                numCS = CSList.length;
+            }
+            float[] dstColor;
+            if (dstHasAlpha) {
+                dstColor = new float[dstNumComp + 1];
+            } else {
+                dstColor = new float[dstNumComp];
+            }
+            Object spixel = null;
+            Object dpixel = null;
+            float[] color = null;
+            float[] tmpColor;
+            // process each pixel
+            for (int y = 0; y < h; y++) {
+                for (int x = 0; x < w; x++) {
+                    spixel = srcRas.getDataElements(x, y, spixel);
+                    color = srcCM.getNormalizedComponents(spixel, color, 0);
+                    tmpColor = srcColorSpace.toCIEXYZ(color);
+                    for (int i = 0; i < numCS; i++) {
+                        tmpColor = CSList[i].fromCIEXYZ(tmpColor);
+                        tmpColor = CSList[i].toCIEXYZ(tmpColor);
+                    }
+                    tmpColor = dstColorSpace.fromCIEXYZ(tmpColor);
+                    for (int i = 0; i < dstNumComp; i++) {
+                        dstColor[i] = tmpColor[i];
+                    }
+                    if (needSrcAlpha) {
+                        dstColor[dstNumComp] = color[srcNumComp];
+                    } else if (dstHasAlpha) {
+                        dstColor[dstNumComp] = 1.0f;
+                    }
+                    dpixel = dstCM.getDataElements(dstColor, 0, dpixel);
+                    dstRas.setDataElements(x, y, dpixel);
+
+                }
+            }
+        }
+
+        return dst;
+    }
+
+    /* color convert a Raster - handles byte, ushort, int, short, float,
+       or double transferTypes */
+    private final WritableRaster nonICCRasterFilter(Raster src,
+                                                    WritableRaster dst)  {
+
+        if (CSList.length != 2) {
+            throw new IllegalArgumentException(
+                "Destination ColorSpace is undefined");
+        }
+        if (src.getNumBands() != CSList[0].getNumComponents()) {
+            throw new IllegalArgumentException(
+                "Numbers of source Raster bands and source color space " +
+                "components do not match");
+        }
+        if (dst == null) {
+            dst = createCompatibleDestRaster(src);
+        } else {
+            if (src.getHeight() != dst.getHeight() ||
+                src.getWidth() != dst.getWidth()) {
+                throw new IllegalArgumentException(
+                    "Width or height of Rasters do not match");
+            }
+            if (dst.getNumBands() != CSList[1].getNumComponents()) {
+                throw new IllegalArgumentException(
+                    "Numbers of destination Raster bands and destination " +
+                    "color space components do not match");
+            }
+        }
+
+        if (srcMinVals == null) {
+            getMinMaxValsFromColorSpaces(CSList[0], CSList[1]);
+        }
+
+        SampleModel srcSM = src.getSampleModel();
+        SampleModel dstSM = dst.getSampleModel();
+        boolean srcIsFloat, dstIsFloat;
+        int srcTransferType = src.getTransferType();
+        int dstTransferType = dst.getTransferType();
+        if ((srcTransferType == DataBuffer.TYPE_FLOAT) ||
+            (srcTransferType == DataBuffer.TYPE_DOUBLE)) {
+            srcIsFloat = true;
+        } else {
+            srcIsFloat = false;
+        }
+        if ((dstTransferType == DataBuffer.TYPE_FLOAT) ||
+            (dstTransferType == DataBuffer.TYPE_DOUBLE)) {
+            dstIsFloat = true;
+        } else {
+            dstIsFloat = false;
+        }
+        int w = src.getWidth();
+        int h = src.getHeight();
+        int srcNumBands = src.getNumBands();
+        int dstNumBands = dst.getNumBands();
+        float[] srcScaleFactor = null;
+        float[] dstScaleFactor = null;
+        if (!srcIsFloat) {
+            srcScaleFactor = new float[srcNumBands];
+            for (int i = 0; i < srcNumBands; i++) {
+                if (srcTransferType == DataBuffer.TYPE_SHORT) {
+                    srcScaleFactor[i] = (srcMaxVals[i] - srcMinVals[i]) /
+                                        32767.0f;
+                } else {
+                    srcScaleFactor[i] = (srcMaxVals[i] - srcMinVals[i]) /
+                        ((float) ((1 << srcSM.getSampleSize(i)) - 1));
+                }
+            }
+        }
+        if (!dstIsFloat) {
+            dstScaleFactor = new float[dstNumBands];
+            for (int i = 0; i < dstNumBands; i++) {
+                if (dstTransferType == DataBuffer.TYPE_SHORT) {
+                    dstScaleFactor[i] = 32767.0f /
+                                        (dstMaxVals[i] - dstMinVals[i]);
+                } else {
+                    dstScaleFactor[i] =
+                        ((float) ((1 << dstSM.getSampleSize(i)) - 1)) /
+                        (dstMaxVals[i] - dstMinVals[i]);
+                }
+            }
+        }
+        int ys = src.getMinY();
+        int yd = dst.getMinY();
+        int xs, xd;
+        float sample;
+        float[] color = new float[srcNumBands];
+        float[] tmpColor;
+        ColorSpace srcColorSpace = CSList[0];
+        ColorSpace dstColorSpace = CSList[1];
+        // process each pixel
+        for (int y = 0; y < h; y++, ys++, yd++) {
+            // get src scanline
+            xs = src.getMinX();
+            xd = dst.getMinX();
+            for (int x = 0; x < w; x++, xs++, xd++) {
+                for (int i = 0; i < srcNumBands; i++) {
+                    sample = src.getSampleFloat(xs, ys, i);
+                    if (!srcIsFloat) {
+                        sample = sample * srcScaleFactor[i] + srcMinVals[i];
+                    }
+                    color[i] = sample;
+                }
+                tmpColor = srcColorSpace.toCIEXYZ(color);
+                tmpColor = dstColorSpace.fromCIEXYZ(tmpColor);
+                for (int i = 0; i < dstNumBands; i++) {
+                    sample = tmpColor[i];
+                    if (!dstIsFloat) {
+                        sample = (sample - dstMinVals[i]) * dstScaleFactor[i];
+                    }
+                    dst.setSample(xd, yd, i, sample);
+                }
+            }
+        }
+        return dst;
+    }
+
+    private void getMinMaxValsFromProfiles(ICC_Profile srcProfile,
+                                           ICC_Profile dstProfile) {
+        int type = srcProfile.getColorSpaceType();
+        int nc = srcProfile.getNumComponents();
+        srcMinVals = new float[nc];
+        srcMaxVals = new float[nc];
+        setMinMax(type, nc, srcMinVals, srcMaxVals);
+        type = dstProfile.getColorSpaceType();
+        nc = dstProfile.getNumComponents();
+        dstMinVals = new float[nc];
+        dstMaxVals = new float[nc];
+        setMinMax(type, nc, dstMinVals, dstMaxVals);
+    }
+
+    private void setMinMax(int type, int nc, float[] minVals, float[] maxVals) {
+        if (type == ColorSpace.TYPE_Lab) {
+            minVals[0] = 0.0f;    // L
+            maxVals[0] = 100.0f;
+            minVals[1] = -128.0f; // a
+            maxVals[1] = 127.0f;
+            minVals[2] = -128.0f; // b
+            maxVals[2] = 127.0f;
+        } else if (type == ColorSpace.TYPE_XYZ) {
+            minVals[0] = minVals[1] = minVals[2] = 0.0f; // X, Y, Z
+            maxVals[0] = maxVals[1] = maxVals[2] = 1.0f + (32767.0f/ 32768.0f);
+        } else {
+            for (int i = 0; i < nc; i++) {
+                minVals[i] = 0.0f;
+                maxVals[i] = 1.0f;
+            }
+        }
+    }
+
+    private void getMinMaxValsFromColorSpaces(ColorSpace srcCspace,
+                                              ColorSpace dstCspace) {
+        int nc = srcCspace.getNumComponents();
+        srcMinVals = new float[nc];
+        srcMaxVals = new float[nc];
+        for (int i = 0; i < nc; i++) {
+            srcMinVals[i] = srcCspace.getMinValue(i);
+            srcMaxVals[i] = srcCspace.getMaxValue(i);
+        }
+        nc = dstCspace.getNumComponents();
+        dstMinVals = new float[nc];
+        dstMaxVals = new float[nc];
+        for (int i = 0; i < nc; i++) {
+            dstMinVals[i] = dstCspace.getMinValue(i);
+            dstMaxVals[i] = dstCspace.getMaxValue(i);
+        }
+    }
+
+}