1/*
2 * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26/*
27 **********************************************************************
28 **********************************************************************
29 **********************************************************************
30 *** COPYRIGHT (c) Eastman Kodak Company, 1997                      ***
31 *** As  an unpublished  work pursuant to Title 17 of the United    ***
32 *** States Code.  All rights reserved.                             ***
33 **********************************************************************
34 **********************************************************************
35 **********************************************************************/
36
37package java.awt.image;
38
39import java.awt.Point;
40import java.awt.Graphics2D;
41import java.awt.color.*;
42import sun.java2d.cmm.ColorTransform;
43import sun.java2d.cmm.CMSManager;
44import sun.java2d.cmm.ProfileDeferralMgr;
45import sun.java2d.cmm.PCMM;
46import java.awt.geom.Rectangle2D;
47import java.awt.geom.Point2D;
48import java.awt.RenderingHints;
49
50/**
51 * This class performs a pixel-by-pixel color conversion of the data in
52 * the source image.  The resulting color values are scaled to the precision
53 * of the destination image.  Color conversion can be specified
54 * via an array of ColorSpace objects or an array of ICC_Profile objects.
55 * <p>
56 * If the source is a BufferedImage with premultiplied alpha, the
57 * color components are divided by the alpha component before color conversion.
58 * If the destination is a BufferedImage with premultiplied alpha, the
59 * color components are multiplied by the alpha component after conversion.
60 * Rasters are treated as having no alpha channel, i.e. all bands are
61 * color bands.
62 * <p>
63 * If a RenderingHints object is specified in the constructor, the
64 * color rendering hint and the dithering hint may be used to control
65 * color conversion.
66 * <p>
67 * Note that Source and Destination may be the same object.
68 * @see java.awt.RenderingHints#KEY_COLOR_RENDERING
69 * @see java.awt.RenderingHints#KEY_DITHERING
70 */
71public class ColorConvertOp implements BufferedImageOp, RasterOp {
72    ICC_Profile[]    profileList;
73    ColorSpace[]     CSList;
74    ColorTransform    thisTransform, thisRasterTransform;
75    ICC_Profile      thisSrcProfile, thisDestProfile;
76    RenderingHints   hints;
77    boolean          gotProfiles;
78    float[]          srcMinVals, srcMaxVals, dstMinVals, dstMaxVals;
79
80    /* the class initializer */
81    static {
82        if (ProfileDeferralMgr.deferring) {
83            ProfileDeferralMgr.activateProfiles();
84        }
85    }
86
87    /**
88     * Constructs a new ColorConvertOp which will convert
89     * from a source color space to a destination color space.
90     * The RenderingHints argument may be null.
91     * This Op can be used only with BufferedImages, and will convert
92     * directly from the ColorSpace of the source image to that of the
93     * destination.  The destination argument of the filter method
94     * cannot be specified as null.
95     * @param hints the {@code RenderingHints} object used to control
96     *        the color conversion, or {@code null}
97     */
98    public ColorConvertOp (RenderingHints hints)
99    {
100        profileList = new ICC_Profile [0];    /* 0 length list */
101        this.hints  = hints;
102    }
103
104    /**
105     * Constructs a new ColorConvertOp from a ColorSpace object.
106     * The RenderingHints argument may be null.  This
107     * Op can be used only with BufferedImages, and is primarily useful
108     * when the {@link #filter(BufferedImage, BufferedImage) filter}
109     * method is invoked with a destination argument of null.
110     * In that case, the ColorSpace defines the destination color space
111     * for the destination created by the filter method.  Otherwise, the
112     * ColorSpace defines an intermediate space to which the source is
113     * converted before being converted to the destination space.
114     * @param cspace defines the destination {@code ColorSpace} or an
115     *        intermediate {@code ColorSpace}
116     * @param hints the {@code RenderingHints} object used to control
117     *        the color conversion, or {@code null}
118     * @throws NullPointerException if cspace is null
119     */
120    public ColorConvertOp (ColorSpace cspace, RenderingHints hints)
121    {
122        if (cspace == null) {
123            throw new NullPointerException("ColorSpace cannot be null");
124        }
125        if (cspace instanceof ICC_ColorSpace) {
126            profileList = new ICC_Profile [1];    /* 1 profile in the list */
127
128            profileList [0] = ((ICC_ColorSpace) cspace).getProfile();
129        }
130        else {
131            CSList = new ColorSpace[1]; /* non-ICC case: 1 ColorSpace in list */
132            CSList[0] = cspace;
133        }
134        this.hints  = hints;
135    }
136
137
138    /**
139     * Constructs a new ColorConvertOp from two ColorSpace objects.
140     * The RenderingHints argument may be null.
141     * This Op is primarily useful for calling the filter method on
142     * Rasters, in which case the two ColorSpaces define the operation
143     * to be performed on the Rasters.  In that case, the number of bands
144     * in the source Raster must match the number of components in
145     * srcCspace, and the number of bands in the destination Raster
146     * must match the number of components in dstCspace.  For BufferedImages,
147     * the two ColorSpaces define intermediate spaces through which the
148     * source is converted before being converted to the destination space.
149     * @param srcCspace the source {@code ColorSpace}
150     * @param dstCspace the destination {@code ColorSpace}
151     * @param hints the {@code RenderingHints} object used to control
152     *        the color conversion, or {@code null}
153     * @throws NullPointerException if either srcCspace or dstCspace is null
154     */
155    public ColorConvertOp(ColorSpace srcCspace, ColorSpace dstCspace,
156                           RenderingHints hints)
157    {
158        if ((srcCspace == null) || (dstCspace == null)) {
159            throw new NullPointerException("ColorSpaces cannot be null");
160        }
161        if ((srcCspace instanceof ICC_ColorSpace) &&
162            (dstCspace instanceof ICC_ColorSpace)) {
163            profileList = new ICC_Profile [2];    /* 2 profiles in the list */
164
165            profileList [0] = ((ICC_ColorSpace) srcCspace).getProfile();
166            profileList [1] = ((ICC_ColorSpace) dstCspace).getProfile();
167
168            getMinMaxValsFromColorSpaces(srcCspace, dstCspace);
169        } else {
170            /* non-ICC case: 2 ColorSpaces in list */
171            CSList = new ColorSpace[2];
172            CSList[0] = srcCspace;
173            CSList[1] = dstCspace;
174        }
175        this.hints  = hints;
176    }
177
178
179     /**
180     * Constructs a new ColorConvertOp from an array of ICC_Profiles.
181     * The RenderingHints argument may be null.
182     * The sequence of profiles may include profiles that represent color
183     * spaces, profiles that represent effects, etc.  If the whole sequence
184     * does not represent a well-defined color conversion, an exception is
185     * thrown.
186     * <p>For BufferedImages, if the ColorSpace
187     * of the source BufferedImage does not match the requirements of the
188     * first profile in the array,
189     * the first conversion is to an appropriate ColorSpace.
190     * If the requirements of the last profile in the array are not met
191     * by the ColorSpace of the destination BufferedImage,
192     * the last conversion is to the destination's ColorSpace.
193     * <p>For Rasters, the number of bands in the source Raster must match
194     * the requirements of the first profile in the array, and the
195     * number of bands in the destination Raster must match the requirements
196     * of the last profile in the array.  The array must have at least two
197     * elements or calling the filter method for Rasters will throw an
198     * IllegalArgumentException.
199     * @param profiles the array of {@code ICC_Profile} objects
200     * @param hints the {@code RenderingHints} object used to control
201     *        the color conversion, or {@code null}
202     * @exception IllegalArgumentException when the profile sequence does not
203     *             specify a well-defined color conversion
204     * @exception NullPointerException if profiles is null
205     */
206    public ColorConvertOp (ICC_Profile[] profiles, RenderingHints hints)
207    {
208        if (profiles == null) {
209            throw new NullPointerException("Profiles cannot be null");
210        }
211        gotProfiles = true;
212        profileList = new ICC_Profile[profiles.length];
213        for (int i1 = 0; i1 < profiles.length; i1++) {
214            profileList[i1] = profiles[i1];
215        }
216        this.hints  = hints;
217    }
218
219
220    /**
221     * Returns the array of ICC_Profiles used to construct this ColorConvertOp.
222     * Returns null if the ColorConvertOp was not constructed from such an
223     * array.
224     * @return the array of {@code ICC_Profile} objects of this
225     *         {@code ColorConvertOp}, or {@code null} if this
226     *         {@code ColorConvertOp} was not constructed with an
227     *         array of {@code ICC_Profile} objects.
228     */
229    public final ICC_Profile[] getICC_Profiles() {
230        if (gotProfiles) {
231            ICC_Profile[] profiles = new ICC_Profile[profileList.length];
232            for (int i1 = 0; i1 < profileList.length; i1++) {
233                profiles[i1] = profileList[i1];
234            }
235            return profiles;
236        }
237        return null;
238    }
239
240    /**
241     * ColorConverts the source BufferedImage.
242     * If the destination image is null,
243     * a BufferedImage will be created with an appropriate ColorModel.
244     * @param src the source {@code BufferedImage} to be converted
245     * @param dest the destination {@code BufferedImage},
246     *        or {@code null}
247     * @return {@code dest} color converted from {@code src}
248     *         or a new, converted {@code BufferedImage}
249     *         if {@code dest} is {@code null}
250     * @exception IllegalArgumentException if dest is null and this op was
251     *             constructed using the constructor which takes only a
252     *             RenderingHints argument, since the operation is ill defined.
253     */
254    public final BufferedImage filter(BufferedImage src, BufferedImage dest) {
255        ColorSpace srcColorSpace, destColorSpace;
256        BufferedImage savdest = null;
257
258        if (src.getColorModel() instanceof IndexColorModel) {
259            IndexColorModel icm = (IndexColorModel) src.getColorModel();
260            src = icm.convertToIntDiscrete(src.getRaster(), true);
261        }
262        srcColorSpace = src.getColorModel().getColorSpace();
263        if (dest != null) {
264            if (dest.getColorModel() instanceof IndexColorModel) {
265                savdest = dest;
266                dest = null;
267                destColorSpace = null;
268            } else {
269                destColorSpace = dest.getColorModel().getColorSpace();
270            }
271        } else {
272            destColorSpace = null;
273        }
274
275        if ((CSList != null) ||
276            (!(srcColorSpace instanceof ICC_ColorSpace)) ||
277            ((dest != null) &&
278             (!(destColorSpace instanceof ICC_ColorSpace)))) {
279            /* non-ICC case */
280            dest = nonICCBIFilter(src, srcColorSpace, dest, destColorSpace);
281        } else {
282            dest = ICCBIFilter(src, srcColorSpace, dest, destColorSpace);
283        }
284
285        if (savdest != null) {
286            Graphics2D big = savdest.createGraphics();
287            try {
288                big.drawImage(dest, 0, 0, null);
289            } finally {
290                big.dispose();
291            }
292            return savdest;
293        } else {
294            return dest;
295        }
296    }
297
298    private final BufferedImage ICCBIFilter(BufferedImage src,
299                                            ColorSpace srcColorSpace,
300                                            BufferedImage dest,
301                                            ColorSpace destColorSpace) {
302    int              nProfiles = profileList.length;
303    ICC_Profile      srcProfile = null, destProfile = null;
304
305        srcProfile = ((ICC_ColorSpace) srcColorSpace).getProfile();
306
307        if (dest == null) {        /* last profile in the list defines
308                                      the output color space */
309            if (nProfiles == 0) {
310                throw new IllegalArgumentException(
311                    "Destination ColorSpace is undefined");
312            }
313            destProfile = profileList [nProfiles - 1];
314            dest = createCompatibleDestImage(src, null);
315        }
316        else {
317            if (src.getHeight() != dest.getHeight() ||
318                src.getWidth() != dest.getWidth()) {
319                throw new IllegalArgumentException(
320                    "Width or height of BufferedImages do not match");
321            }
322            destProfile = ((ICC_ColorSpace) destColorSpace).getProfile();
323        }
324
325        /* Checking if all profiles in the transform sequence are the same.
326         * If so, performing just copying the data.
327         */
328        if (srcProfile == destProfile) {
329            boolean noTrans = true;
330            for (int i = 0; i < nProfiles; i++) {
331                if (srcProfile != profileList[i]) {
332                    noTrans = false;
333                    break;
334                }
335            }
336            if (noTrans) {
337                Graphics2D g = dest.createGraphics();
338                try {
339                    g.drawImage(src, 0, 0, null);
340                } finally {
341                    g.dispose();
342                }
343
344                return dest;
345            }
346        }
347
348        /* make a new transform if needed */
349        if ((thisTransform == null) || (thisSrcProfile != srcProfile) ||
350            (thisDestProfile != destProfile) ) {
351            updateBITransform(srcProfile, destProfile);
352        }
353
354        /* color convert the image */
355        thisTransform.colorConvert(src, dest);
356
357        return dest;
358    }
359
360    private void updateBITransform(ICC_Profile srcProfile,
361                                   ICC_Profile destProfile) {
362        ICC_Profile[]    theProfiles;
363        int              i1, nProfiles, nTransforms, whichTrans, renderState;
364        ColorTransform[]  theTransforms;
365        boolean          useSrc = false, useDest = false;
366
367        nProfiles = profileList.length;
368        nTransforms = nProfiles;
369        if ((nProfiles == 0) || (srcProfile != profileList[0])) {
370            nTransforms += 1;
371            useSrc = true;
372        }
373        if ((nProfiles == 0) || (destProfile != profileList[nProfiles - 1]) ||
374            (nTransforms < 2)) {
375            nTransforms += 1;
376            useDest = true;
377        }
378
379        /* make the profile list */
380        theProfiles = new ICC_Profile[nTransforms]; /* the list of profiles
381                                                       for this Op */
382
383        int idx = 0;
384        if (useSrc) {
385            /* insert source as first profile */
386            theProfiles[idx++] = srcProfile;
387        }
388
389        for (i1 = 0; i1 < nProfiles; i1++) {
390                                   /* insert profiles defined in this Op */
391            theProfiles[idx++] = profileList [i1];
392        }
393
394        if (useDest) {
395            /* insert dest as last profile */
396            theProfiles[idx] = destProfile;
397        }
398
399        /* make the transform list */
400        theTransforms = new ColorTransform [nTransforms];
401
402        /* initialize transform get loop */
403        if (theProfiles[0].getProfileClass() == ICC_Profile.CLASS_OUTPUT) {
404                                        /* if first profile is a printer
405                                           render as colorimetric */
406            renderState = ICC_Profile.icRelativeColorimetric;
407        }
408        else {
409            renderState = ICC_Profile.icPerceptual; /* render any other
410                                                       class perceptually */
411        }
412
413        whichTrans = ColorTransform.In;
414
415        PCMM mdl = CMSManager.getModule();
416
417        /* get the transforms from each profile */
418        for (i1 = 0; i1 < nTransforms; i1++) {
419            if (i1 == nTransforms -1) {         /* last profile? */
420                whichTrans = ColorTransform.Out; /* get output transform */
421            }
422            else {      /* check for abstract profile */
423                if ((whichTrans == ColorTransform.Simulation) &&
424                    (theProfiles[i1].getProfileClass () ==
425                     ICC_Profile.CLASS_ABSTRACT)) {
426                renderState = ICC_Profile.icPerceptual;
427                    whichTrans = ColorTransform.In;
428                }
429            }
430
431            theTransforms[i1] = mdl.createTransform (
432                theProfiles[i1], renderState, whichTrans);
433
434            /* get this profile's rendering intent to select transform
435               from next profile */
436            renderState = getRenderingIntent(theProfiles[i1]);
437
438            /* "middle" profiles use simulation transform */
439            whichTrans = ColorTransform.Simulation;
440        }
441
442        /* make the net transform */
443        thisTransform = mdl.createTransform(theTransforms);
444
445        /* update corresponding source and dest profiles */
446        thisSrcProfile = srcProfile;
447        thisDestProfile = destProfile;
448    }
449
450    /**
451     * ColorConverts the image data in the source Raster.
452     * If the destination Raster is null, a new Raster will be created.
453     * The number of bands in the source and destination Rasters must
454     * meet the requirements explained above.  The constructor used to
455     * create this ColorConvertOp must have provided enough information
456     * to define both source and destination color spaces.  See above.
457     * Otherwise, an exception is thrown.
458     * @param src the source {@code Raster} to be converted
459     * @param dest the destination {@code WritableRaster},
460     *        or {@code null}
461     * @return {@code dest} color converted from {@code src}
462     *         or a new, converted {@code WritableRaster}
463     *         if {@code dest} is {@code null}
464     * @exception IllegalArgumentException if the number of source or
465     *             destination bands is incorrect, the source or destination
466     *             color spaces are undefined, or this op was constructed
467     *             with one of the constructors that applies only to
468     *             operations on BufferedImages.
469     */
470    public final WritableRaster filter (Raster src, WritableRaster dest)  {
471
472        if (CSList != null) {
473            /* non-ICC case */
474            return nonICCRasterFilter(src, dest);
475        }
476        int nProfiles = profileList.length;
477        if (nProfiles < 2) {
478            throw new IllegalArgumentException(
479                "Source or Destination ColorSpace is undefined");
480        }
481        if (src.getNumBands() != profileList[0].getNumComponents()) {
482            throw new IllegalArgumentException(
483                "Numbers of source Raster bands and source color space " +
484                "components do not match");
485        }
486        if (dest == null) {
487            dest = createCompatibleDestRaster(src);
488        }
489        else {
490            if (src.getHeight() != dest.getHeight() ||
491                src.getWidth() != dest.getWidth()) {
492                throw new IllegalArgumentException(
493                    "Width or height of Rasters do not match");
494            }
495            if (dest.getNumBands() !=
496                profileList[nProfiles-1].getNumComponents()) {
497                throw new IllegalArgumentException(
498                    "Numbers of destination Raster bands and destination " +
499                    "color space components do not match");
500            }
501        }
502
503        /* make a new transform if needed */
504        if (thisRasterTransform == null) {
505            int              i1, whichTrans, renderState;
506            ColorTransform[]  theTransforms;
507
508            /* make the transform list */
509            theTransforms = new ColorTransform [nProfiles];
510
511            /* initialize transform get loop */
512            if (profileList[0].getProfileClass() == ICC_Profile.CLASS_OUTPUT) {
513                                            /* if first profile is a printer
514                                               render as colorimetric */
515                renderState = ICC_Profile.icRelativeColorimetric;
516            }
517            else {
518                renderState = ICC_Profile.icPerceptual; /* render any other
519                                                           class perceptually */
520            }
521
522            whichTrans = ColorTransform.In;
523
524            PCMM mdl = CMSManager.getModule();
525
526            /* get the transforms from each profile */
527            for (i1 = 0; i1 < nProfiles; i1++) {
528                if (i1 == nProfiles -1) {         /* last profile? */
529                    whichTrans = ColorTransform.Out; /* get output transform */
530                }
531                else {  /* check for abstract profile */
532                    if ((whichTrans == ColorTransform.Simulation) &&
533                        (profileList[i1].getProfileClass () ==
534                         ICC_Profile.CLASS_ABSTRACT)) {
535                        renderState = ICC_Profile.icPerceptual;
536                        whichTrans = ColorTransform.In;
537                    }
538                }
539
540                theTransforms[i1] = mdl.createTransform (
541                    profileList[i1], renderState, whichTrans);
542
543                /* get this profile's rendering intent to select transform
544                   from next profile */
545                renderState = getRenderingIntent(profileList[i1]);
546
547                /* "middle" profiles use simulation transform */
548                whichTrans = ColorTransform.Simulation;
549            }
550
551            /* make the net transform */
552            thisRasterTransform = mdl.createTransform(theTransforms);
553        }
554
555        int srcTransferType = src.getTransferType();
556        int dstTransferType = dest.getTransferType();
557        if ((srcTransferType == DataBuffer.TYPE_FLOAT) ||
558            (srcTransferType == DataBuffer.TYPE_DOUBLE) ||
559            (dstTransferType == DataBuffer.TYPE_FLOAT) ||
560            (dstTransferType == DataBuffer.TYPE_DOUBLE)) {
561            if (srcMinVals == null) {
562                getMinMaxValsFromProfiles(profileList[0],
563                                          profileList[nProfiles-1]);
564            }
565            /* color convert the raster */
566            thisRasterTransform.colorConvert(src, dest,
567                                             srcMinVals, srcMaxVals,
568                                             dstMinVals, dstMaxVals);
569        } else {
570            /* color convert the raster */
571            thisRasterTransform.colorConvert(src, dest);
572        }
573
574
575        return dest;
576    }
577
578    /**
579     * Returns the bounding box of the destination, given this source.
580     * Note that this will be the same as the bounding box of the
581     * source.
582     * @param src the source {@code BufferedImage}
583     * @return a {@code Rectangle2D} that is the bounding box
584     *         of the destination, given the specified {@code src}
585     */
586    public final Rectangle2D getBounds2D (BufferedImage src) {
587        return getBounds2D(src.getRaster());
588    }
589
590    /**
591     * Returns the bounding box of the destination, given this source.
592     * Note that this will be the same as the bounding box of the
593     * source.
594     * @param src the source {@code Raster}
595     * @return a {@code Rectangle2D} that is the bounding box
596     *         of the destination, given the specified {@code src}
597     */
598    public final Rectangle2D getBounds2D (Raster src) {
599        /*        return new Rectangle (src.getXOffset(),
600                              src.getYOffset(),
601                              src.getWidth(), src.getHeight()); */
602        return src.getBounds();
603    }
604
605    /**
606     * Creates a zeroed destination image with the correct size and number of
607     * bands, given this source.
608     * @param src       Source image for the filter operation.
609     * @param destCM    ColorModel of the destination.  If null, an
610     *                  appropriate ColorModel will be used.
611     * @return a {@code BufferedImage} with the correct size and
612     * number of bands from the specified {@code src}.
613     * @throws IllegalArgumentException if {@code destCM} is
614     *         {@code null} and this {@code ColorConvertOp} was
615     *         created without any {@code ICC_Profile} or
616     *         {@code ColorSpace} defined for the destination
617     */
618    public BufferedImage createCompatibleDestImage (BufferedImage src,
619                                                    ColorModel destCM) {
620        ColorSpace cs = null;;
621        if (destCM == null) {
622            if (CSList == null) {
623                /* ICC case */
624                int nProfiles = profileList.length;
625                if (nProfiles == 0) {
626                    throw new IllegalArgumentException(
627                        "Destination ColorSpace is undefined");
628                }
629                ICC_Profile destProfile = profileList[nProfiles - 1];
630                cs = new ICC_ColorSpace(destProfile);
631            } else {
632                /* non-ICC case */
633                int nSpaces = CSList.length;
634                cs = CSList[nSpaces - 1];
635            }
636        }
637        return createCompatibleDestImage(src, destCM, cs);
638    }
639
640    private BufferedImage createCompatibleDestImage(BufferedImage src,
641                                                    ColorModel destCM,
642                                                    ColorSpace destCS) {
643        BufferedImage image;
644        if (destCM == null) {
645            ColorModel srcCM = src.getColorModel();
646            int nbands = destCS.getNumComponents();
647            boolean hasAlpha = srcCM.hasAlpha();
648            if (hasAlpha) {
649               nbands += 1;
650            }
651            int[] nbits = new int[nbands];
652            for (int i = 0; i < nbands; i++) {
653                nbits[i] = 8;
654            }
655            destCM = new ComponentColorModel(destCS, nbits, hasAlpha,
656                                             srcCM.isAlphaPremultiplied(),
657                                             srcCM.getTransparency(),
658                                             DataBuffer.TYPE_BYTE);
659        }
660        int w = src.getWidth();
661        int h = src.getHeight();
662        image = new BufferedImage(destCM,
663                                  destCM.createCompatibleWritableRaster(w, h),
664                                  destCM.isAlphaPremultiplied(), null);
665        return image;
666    }
667
668
669    /**
670     * Creates a zeroed destination Raster with the correct size and number of
671     * bands, given this source.
672     * @param src the specified {@code Raster}
673     * @return a {@code WritableRaster} with the correct size and number
674     *         of bands from the specified {@code src}
675     * @throws IllegalArgumentException if this {@code ColorConvertOp}
676     *         was created without sufficient information to define the
677     *         {@code dst} and {@code src} color spaces
678     */
679    public WritableRaster createCompatibleDestRaster (Raster src) {
680        int ncomponents;
681
682        if (CSList != null) {
683            /* non-ICC case */
684            if (CSList.length != 2) {
685                throw new IllegalArgumentException(
686                    "Destination ColorSpace is undefined");
687            }
688            ncomponents = CSList[1].getNumComponents();
689        } else {
690            /* ICC case */
691            int nProfiles = profileList.length;
692            if (nProfiles < 2) {
693                throw new IllegalArgumentException(
694                    "Destination ColorSpace is undefined");
695            }
696            ncomponents = profileList[nProfiles-1].getNumComponents();
697        }
698
699        WritableRaster dest =
700            Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE,
701                                  src.getWidth(),
702                                  src.getHeight(),
703                                  ncomponents,
704                                  new Point(src.getMinX(), src.getMinY()));
705        return dest;
706    }
707
708    /**
709     * Returns the location of the destination point given a
710     * point in the source.  If {@code dstPt} is non-null,
711     * it will be used to hold the return value.  Note that
712     * for this class, the destination point will be the same
713     * as the source point.
714     * @param srcPt the specified source {@code Point2D}
715     * @param dstPt the destination {@code Point2D}
716     * @return {@code dstPt} after setting its location to be
717     *         the same as {@code srcPt}
718     */
719    public final Point2D getPoint2D (Point2D srcPt, Point2D dstPt) {
720        if (dstPt == null) {
721            dstPt = new Point2D.Float();
722        }
723        dstPt.setLocation(srcPt.getX(), srcPt.getY());
724
725        return dstPt;
726    }
727
728
729    /**
730     * Returns the RenderingIntent from the specified ICC Profile.
731     */
732    private int getRenderingIntent (ICC_Profile profile) {
733        byte[] header = profile.getData(ICC_Profile.icSigHead);
734        int index = ICC_Profile.icHdrRenderingIntent;
735
736        /* According to ICC spec, only the least-significant 16 bits shall be
737         * used to encode the rendering intent. The most significant 16 bits
738         * shall be set to zero. Thus, we are ignoring two most significant
739         * bytes here.
740         *
741         *  See http://www.color.org/ICC1v42_2006-05.pdf, section 7.2.15.
742         */
743        return ((header[index+2] & 0xff) <<  8) |
744                (header[index+3] & 0xff);
745    }
746
747    /**
748     * Returns the rendering hints used by this op.
749     * @return the {@code RenderingHints} object of this
750     *         {@code ColorConvertOp}
751     */
752    public final RenderingHints getRenderingHints() {
753        return hints;
754    }
755
756    private final BufferedImage nonICCBIFilter(BufferedImage src,
757                                               ColorSpace srcColorSpace,
758                                               BufferedImage dst,
759                                               ColorSpace dstColorSpace) {
760
761        int w = src.getWidth();
762        int h = src.getHeight();
763        ICC_ColorSpace ciespace =
764            (ICC_ColorSpace) ColorSpace.getInstance(ColorSpace.CS_CIEXYZ);
765        if (dst == null) {
766            dst = createCompatibleDestImage(src, null);
767            dstColorSpace = dst.getColorModel().getColorSpace();
768        } else {
769            if ((h != dst.getHeight()) || (w != dst.getWidth())) {
770                throw new IllegalArgumentException(
771                    "Width or height of BufferedImages do not match");
772            }
773        }
774        Raster srcRas = src.getRaster();
775        WritableRaster dstRas = dst.getRaster();
776        ColorModel srcCM = src.getColorModel();
777        ColorModel dstCM = dst.getColorModel();
778        int srcNumComp = srcCM.getNumColorComponents();
779        int dstNumComp = dstCM.getNumColorComponents();
780        boolean dstHasAlpha = dstCM.hasAlpha();
781        boolean needSrcAlpha = srcCM.hasAlpha() && dstHasAlpha;
782        ColorSpace[] list;
783        if ((CSList == null) && (profileList.length != 0)) {
784            /* possible non-ICC src, some profiles, possible non-ICC dst */
785            boolean nonICCSrc, nonICCDst;
786            ICC_Profile srcProfile, dstProfile;
787            if (!(srcColorSpace instanceof ICC_ColorSpace)) {
788                nonICCSrc = true;
789                srcProfile = ciespace.getProfile();
790            } else {
791                nonICCSrc = false;
792                srcProfile = ((ICC_ColorSpace) srcColorSpace).getProfile();
793            }
794            if (!(dstColorSpace instanceof ICC_ColorSpace)) {
795                nonICCDst = true;
796                dstProfile = ciespace.getProfile();
797            } else {
798                nonICCDst = false;
799                dstProfile = ((ICC_ColorSpace) dstColorSpace).getProfile();
800            }
801            /* make a new transform if needed */
802            if ((thisTransform == null) || (thisSrcProfile != srcProfile) ||
803                (thisDestProfile != dstProfile) ) {
804                updateBITransform(srcProfile, dstProfile);
805            }
806            // process per scanline
807            float maxNum = 65535.0f; // use 16-bit precision in CMM
808            ColorSpace cs;
809            int iccSrcNumComp;
810            if (nonICCSrc) {
811                cs = ciespace;
812                iccSrcNumComp = 3;
813            } else {
814                cs = srcColorSpace;
815                iccSrcNumComp = srcNumComp;
816            }
817            float[] srcMinVal = new float[iccSrcNumComp];
818            float[] srcInvDiffMinMax = new float[iccSrcNumComp];
819            for (int i = 0; i < srcNumComp; i++) {
820                srcMinVal[i] = cs.getMinValue(i);
821                srcInvDiffMinMax[i] = maxNum / (cs.getMaxValue(i) - srcMinVal[i]);
822            }
823            int iccDstNumComp;
824            if (nonICCDst) {
825                cs = ciespace;
826                iccDstNumComp = 3;
827            } else {
828                cs = dstColorSpace;
829                iccDstNumComp = dstNumComp;
830            }
831            float[] dstMinVal = new float[iccDstNumComp];
832            float[] dstDiffMinMax = new float[iccDstNumComp];
833            for (int i = 0; i < dstNumComp; i++) {
834                dstMinVal[i] = cs.getMinValue(i);
835                dstDiffMinMax[i] = (cs.getMaxValue(i) - dstMinVal[i]) / maxNum;
836            }
837            float[] dstColor;
838            if (dstHasAlpha) {
839                int size = ((dstNumComp + 1) > 3) ? (dstNumComp + 1) : 3;
840                dstColor = new float[size];
841            } else {
842                int size = (dstNumComp  > 3) ? dstNumComp : 3;
843                dstColor = new float[size];
844            }
845            short[] srcLine = new short[w * iccSrcNumComp];
846            short[] dstLine = new short[w * iccDstNumComp];
847            Object pixel;
848            float[] color;
849            float[] alpha = null;
850            if (needSrcAlpha) {
851                alpha = new float[w];
852            }
853            int idx;
854            // process each scanline
855            for (int y = 0; y < h; y++) {
856                // convert src scanline
857                pixel = null;
858                color = null;
859                idx = 0;
860                for (int x = 0; x < w; x++) {
861                    pixel = srcRas.getDataElements(x, y, pixel);
862                    color = srcCM.getNormalizedComponents(pixel, color, 0);
863                    if (needSrcAlpha) {
864                        alpha[x] = color[srcNumComp];
865                    }
866                    if (nonICCSrc) {
867                        color = srcColorSpace.toCIEXYZ(color);
868                    }
869                    for (int i = 0; i < iccSrcNumComp; i++) {
870                        srcLine[idx++] = (short)
871                            ((color[i] - srcMinVal[i]) * srcInvDiffMinMax[i] +
872                             0.5f);
873                    }
874                }
875                // color convert srcLine to dstLine
876                thisTransform.colorConvert(srcLine, dstLine);
877                // convert dst scanline
878                pixel = null;
879                idx = 0;
880                for (int x = 0; x < w; x++) {
881                    for (int i = 0; i < iccDstNumComp; i++) {
882                        dstColor[i] = ((float) (dstLine[idx++] & 0xffff)) *
883                                      dstDiffMinMax[i] + dstMinVal[i];
884                    }
885                    if (nonICCDst) {
886                        color = srcColorSpace.fromCIEXYZ(dstColor);
887                        for (int i = 0; i < dstNumComp; i++) {
888                            dstColor[i] = color[i];
889                        }
890                    }
891                    if (needSrcAlpha) {
892                        dstColor[dstNumComp] = alpha[x];
893                    } else if (dstHasAlpha) {
894                        dstColor[dstNumComp] = 1.0f;
895                    }
896                    pixel = dstCM.getDataElements(dstColor, 0, pixel);
897                    dstRas.setDataElements(x, y, pixel);
898                }
899            }
900        } else {
901            /* possible non-ICC src, possible CSList, possible non-ICC dst */
902            // process per pixel
903            int numCS;
904            if (CSList == null) {
905                numCS = 0;
906            } else {
907                numCS = CSList.length;
908            }
909            float[] dstColor;
910            if (dstHasAlpha) {
911                dstColor = new float[dstNumComp + 1];
912            } else {
913                dstColor = new float[dstNumComp];
914            }
915            Object spixel = null;
916            Object dpixel = null;
917            float[] color = null;
918            float[] tmpColor;
919            // process each pixel
920            for (int y = 0; y < h; y++) {
921                for (int x = 0; x < w; x++) {
922                    spixel = srcRas.getDataElements(x, y, spixel);
923                    color = srcCM.getNormalizedComponents(spixel, color, 0);
924                    tmpColor = srcColorSpace.toCIEXYZ(color);
925                    for (int i = 0; i < numCS; i++) {
926                        tmpColor = CSList[i].fromCIEXYZ(tmpColor);
927                        tmpColor = CSList[i].toCIEXYZ(tmpColor);
928                    }
929                    tmpColor = dstColorSpace.fromCIEXYZ(tmpColor);
930                    for (int i = 0; i < dstNumComp; i++) {
931                        dstColor[i] = tmpColor[i];
932                    }
933                    if (needSrcAlpha) {
934                        dstColor[dstNumComp] = color[srcNumComp];
935                    } else if (dstHasAlpha) {
936                        dstColor[dstNumComp] = 1.0f;
937                    }
938                    dpixel = dstCM.getDataElements(dstColor, 0, dpixel);
939                    dstRas.setDataElements(x, y, dpixel);
940
941                }
942            }
943        }
944
945        return dst;
946    }
947
948    /* color convert a Raster - handles byte, ushort, int, short, float,
949       or double transferTypes */
950    private final WritableRaster nonICCRasterFilter(Raster src,
951                                                    WritableRaster dst)  {
952
953        if (CSList.length != 2) {
954            throw new IllegalArgumentException(
955                "Destination ColorSpace is undefined");
956        }
957        if (src.getNumBands() != CSList[0].getNumComponents()) {
958            throw new IllegalArgumentException(
959                "Numbers of source Raster bands and source color space " +
960                "components do not match");
961        }
962        if (dst == null) {
963            dst = createCompatibleDestRaster(src);
964        } else {
965            if (src.getHeight() != dst.getHeight() ||
966                src.getWidth() != dst.getWidth()) {
967                throw new IllegalArgumentException(
968                    "Width or height of Rasters do not match");
969            }
970            if (dst.getNumBands() != CSList[1].getNumComponents()) {
971                throw new IllegalArgumentException(
972                    "Numbers of destination Raster bands and destination " +
973                    "color space components do not match");
974            }
975        }
976
977        if (srcMinVals == null) {
978            getMinMaxValsFromColorSpaces(CSList[0], CSList[1]);
979        }
980
981        SampleModel srcSM = src.getSampleModel();
982        SampleModel dstSM = dst.getSampleModel();
983        boolean srcIsFloat, dstIsFloat;
984        int srcTransferType = src.getTransferType();
985        int dstTransferType = dst.getTransferType();
986        if ((srcTransferType == DataBuffer.TYPE_FLOAT) ||
987            (srcTransferType == DataBuffer.TYPE_DOUBLE)) {
988            srcIsFloat = true;
989        } else {
990            srcIsFloat = false;
991        }
992        if ((dstTransferType == DataBuffer.TYPE_FLOAT) ||
993            (dstTransferType == DataBuffer.TYPE_DOUBLE)) {
994            dstIsFloat = true;
995        } else {
996            dstIsFloat = false;
997        }
998        int w = src.getWidth();
999        int h = src.getHeight();
1000        int srcNumBands = src.getNumBands();
1001        int dstNumBands = dst.getNumBands();
1002        float[] srcScaleFactor = null;
1003        float[] dstScaleFactor = null;
1004        if (!srcIsFloat) {
1005            srcScaleFactor = new float[srcNumBands];
1006            for (int i = 0; i < srcNumBands; i++) {
1007                if (srcTransferType == DataBuffer.TYPE_SHORT) {
1008                    srcScaleFactor[i] = (srcMaxVals[i] - srcMinVals[i]) /
1009                                        32767.0f;
1010                } else {
1011                    srcScaleFactor[i] = (srcMaxVals[i] - srcMinVals[i]) /
1012                        ((float) ((1 << srcSM.getSampleSize(i)) - 1));
1013                }
1014            }
1015        }
1016        if (!dstIsFloat) {
1017            dstScaleFactor = new float[dstNumBands];
1018            for (int i = 0; i < dstNumBands; i++) {
1019                if (dstTransferType == DataBuffer.TYPE_SHORT) {
1020                    dstScaleFactor[i] = 32767.0f /
1021                                        (dstMaxVals[i] - dstMinVals[i]);
1022                } else {
1023                    dstScaleFactor[i] =
1024                        ((float) ((1 << dstSM.getSampleSize(i)) - 1)) /
1025                        (dstMaxVals[i] - dstMinVals[i]);
1026                }
1027            }
1028        }
1029        int ys = src.getMinY();
1030        int yd = dst.getMinY();
1031        int xs, xd;
1032        float sample;
1033        float[] color = new float[srcNumBands];
1034        float[] tmpColor;
1035        ColorSpace srcColorSpace = CSList[0];
1036        ColorSpace dstColorSpace = CSList[1];
1037        // process each pixel
1038        for (int y = 0; y < h; y++, ys++, yd++) {
1039            // get src scanline
1040            xs = src.getMinX();
1041            xd = dst.getMinX();
1042            for (int x = 0; x < w; x++, xs++, xd++) {
1043                for (int i = 0; i < srcNumBands; i++) {
1044                    sample = src.getSampleFloat(xs, ys, i);
1045                    if (!srcIsFloat) {
1046                        sample = sample * srcScaleFactor[i] + srcMinVals[i];
1047                    }
1048                    color[i] = sample;
1049                }
1050                tmpColor = srcColorSpace.toCIEXYZ(color);
1051                tmpColor = dstColorSpace.fromCIEXYZ(tmpColor);
1052                for (int i = 0; i < dstNumBands; i++) {
1053                    sample = tmpColor[i];
1054                    if (!dstIsFloat) {
1055                        sample = (sample - dstMinVals[i]) * dstScaleFactor[i];
1056                    }
1057                    dst.setSample(xd, yd, i, sample);
1058                }
1059            }
1060        }
1061        return dst;
1062    }
1063
1064    private void getMinMaxValsFromProfiles(ICC_Profile srcProfile,
1065                                           ICC_Profile dstProfile) {
1066        int type = srcProfile.getColorSpaceType();
1067        int nc = srcProfile.getNumComponents();
1068        srcMinVals = new float[nc];
1069        srcMaxVals = new float[nc];
1070        setMinMax(type, nc, srcMinVals, srcMaxVals);
1071        type = dstProfile.getColorSpaceType();
1072        nc = dstProfile.getNumComponents();
1073        dstMinVals = new float[nc];
1074        dstMaxVals = new float[nc];
1075        setMinMax(type, nc, dstMinVals, dstMaxVals);
1076    }
1077
1078    private void setMinMax(int type, int nc, float[] minVals, float[] maxVals) {
1079        if (type == ColorSpace.TYPE_Lab) {
1080            minVals[0] = 0.0f;    // L
1081            maxVals[0] = 100.0f;
1082            minVals[1] = -128.0f; // a
1083            maxVals[1] = 127.0f;
1084            minVals[2] = -128.0f; // b
1085            maxVals[2] = 127.0f;
1086        } else if (type == ColorSpace.TYPE_XYZ) {
1087            minVals[0] = minVals[1] = minVals[2] = 0.0f; // X, Y, Z
1088            maxVals[0] = maxVals[1] = maxVals[2] = 1.0f + (32767.0f/ 32768.0f);
1089        } else {
1090            for (int i = 0; i < nc; i++) {
1091                minVals[i] = 0.0f;
1092                maxVals[i] = 1.0f;
1093            }
1094        }
1095    }
1096
1097    private void getMinMaxValsFromColorSpaces(ColorSpace srcCspace,
1098                                              ColorSpace dstCspace) {
1099        int nc = srcCspace.getNumComponents();
1100        srcMinVals = new float[nc];
1101        srcMaxVals = new float[nc];
1102        for (int i = 0; i < nc; i++) {
1103            srcMinVals[i] = srcCspace.getMinValue(i);
1104            srcMaxVals[i] = srcCspace.getMaxValue(i);
1105        }
1106        nc = dstCspace.getNumComponents();
1107        dstMinVals = new float[nc];
1108        dstMaxVals = new float[nc];
1109        for (int i = 0; i < nc; i++) {
1110            dstMinVals[i] = dstCspace.getMinValue(i);
1111            dstMaxVals[i] = dstCspace.getMaxValue(i);
1112        }
1113    }
1114
1115}
1116