KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > java > awt > image > ColorConvertOp


1 /*
2  * @(#)ColorConvertOp.java 1.38 03/12/19
3  *
4  * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
5  * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
6  */

7
8 /**********************************************************************
9  **********************************************************************
10  **********************************************************************
11  *** COPYRIGHT (c) Eastman Kodak Company, 1997 ***
12  *** As an unpublished work pursuant to Title 17 of the United ***
13  *** States Code. All rights reserved. ***
14  **********************************************************************
15  **********************************************************************
16  **********************************************************************/

17
18 package java.awt.image;
19
20 import java.awt.Point JavaDoc;
21 import java.awt.Graphics2D JavaDoc;
22 import java.awt.color.*;
23 import sun.awt.color.ICC_Transform;
24 import sun.awt.color.ProfileDeferralMgr;
25 import java.awt.geom.Rectangle2D JavaDoc;
26 import java.awt.geom.Point2D JavaDoc;
27 import java.awt.RenderingHints JavaDoc;
28
29 /**
30  * This class performs a pixel-by-pixel color conversion of the data in
31  * the source image. The resulting color values are scaled to the precision
32  * of the destination image. Color conversion can be specified
33  * via an array of ColorSpace objects or an array of ICC_Profile objects.
34  * <p>
35  * If the source is a BufferedImage with premultiplied alpha, the
36  * color components are divided by the alpha component before color conversion.
37  * If the destination is a BufferedImage with premultiplied alpha, the
38  * color components are multiplied by the alpha component after conversion.
39  * Rasters are treated as having no alpha channel, i.e. all bands are
40  * color bands.
41  * <p>
42  * If a RenderingHints object is specified in the constructor, the
43  * color rendering hint and the dithering hint may be used to control
44  * color conversion.
45  * <p>
46  * Note that Source and Destination may be the same object.
47  * <p>
48  * @see java.awt.RenderingHints#KEY_COLOR_RENDERING
49  * @see java.awt.RenderingHints#KEY_DITHERING
50  */

51 public class ColorConvertOp implements BufferedImageOp JavaDoc, RasterOp JavaDoc {
52     ICC_Profile[] profileList;
53     ColorSpace[] CSList;
54     ICC_Transform thisTransform, thisRasterTransform;
55     ICC_Profile thisSrcProfile, thisDestProfile;
56     RenderingHints JavaDoc hints;
57     boolean gotProfiles;
58     float[] srcMinVals, srcMaxVals, dstMinVals, dstMaxVals;
59
60     /* the class initializer */
61     static {
62         if (ProfileDeferralMgr.deferring) {
63             ProfileDeferralMgr.activateProfiles();
64         }
65     }
66
67     /**
68      * Constructs a new ColorConvertOp which will convert
69      * from a source color space to a destination color space.
70      * The RenderingHints argument may be null.
71      * This Op can be used only with BufferedImages, and will convert
72      * directly from the ColorSpace of the source image to that of the
73      * destination. The destination argument of the filter method
74      * cannot be specified as null.
75      * @param hints the <code>RenderingHints</code> object used to control
76      * the color conversion, or <code>null</code>
77      */

78     public ColorConvertOp (RenderingHints JavaDoc hints)
79     {
80         profileList = new ICC_Profile [0]; /* 0 length list */
81         this.hints = hints;
82     }
83
84     /**
85      * Constructs a new ColorConvertOp from a ColorSpace object.
86      * The RenderingHints argument may be null. This
87      * Op can be used only with BufferedImages, and is primarily useful
88      * when the {@link #filter(BufferedImage, BufferedImage) filter}
89      * method is invoked with a destination argument of null.
90      * In that case, the ColorSpace defines the destination color space
91      * for the destination created by the filter method. Otherwise, the
92      * ColorSpace defines an intermediate space to which the source is
93      * converted before being converted to the destination space.
94      * @param cspace defines the destination <code>ColorSpace</code> or an
95      * intermediate <code>ColorSpace</code>
96      * @param hints the <code>RenderingHints</code> object used to control
97      * the color conversion, or <code>null</code>
98      * @throws NullPointerException if cspace is null
99      */

100     public ColorConvertOp (ColorSpace cspace, RenderingHints JavaDoc hints)
101     {
102         if (cspace == null) {
103             throw new NullPointerException JavaDoc("ColorSpace cannot be null");
104         }
105         if (cspace instanceof ICC_ColorSpace) {
106             profileList = new ICC_Profile [1]; /* 1 profile in the list */
107
108             profileList [0] = ((ICC_ColorSpace) cspace).getProfile();
109         }
110         else {
111             CSList = new ColorSpace[1]; /* non-ICC case: 1 ColorSpace in list */
112             CSList[0] = cspace;
113         }
114         this.hints = hints;
115     }
116
117
118     /**
119      * Constructs a new ColorConvertOp from two ColorSpace objects.
120      * The RenderingHints argument may be null.
121      * This Op is primarily useful for calling the filter method on
122      * Rasters, in which case the two ColorSpaces define the operation
123      * to be performed on the Rasters. In that case, the number of bands
124      * in the source Raster must match the number of components in
125      * srcCspace, and the number of bands in the destination Raster
126      * must match the number of components in dstCspace. For BufferedImages,
127      * the two ColorSpaces define intermediate spaces through which the
128      * source is converted before being converted to the destination space.
129      * @param srcCspace the source <code>ColorSpace</code>
130      * @param dstCspace the destination <code>ColorSpace</code>
131      * @param hints the <code>RenderingHints</code> object used to control
132      * the color conversion, or <code>null</code>
133      * @throws NullPointerException if either srcCspace or dstCspace is null
134      */

135     public ColorConvertOp(ColorSpace srcCspace, ColorSpace dstCspace,
136                            RenderingHints JavaDoc hints)
137     {
138         if ((srcCspace == null) || (dstCspace == null)) {
139             throw new NullPointerException JavaDoc("ColorSpaces cannot be null");
140         }
141         if ((srcCspace instanceof ICC_ColorSpace) &&
142             (dstCspace instanceof ICC_ColorSpace)) {
143             profileList = new ICC_Profile [2]; /* 2 profiles in the list */
144
145             profileList [0] = ((ICC_ColorSpace) srcCspace).getProfile();
146             profileList [1] = ((ICC_ColorSpace) dstCspace).getProfile();
147
148             getMinMaxValsFromColorSpaces(srcCspace, dstCspace);
149         } else {
150             /* non-ICC case: 2 ColorSpaces in list */
151             CSList = new ColorSpace[2];
152             CSList[0] = srcCspace;
153             CSList[1] = dstCspace;
154         }
155         this.hints = hints;
156     }
157
158
159      /**
160      * Constructs a new ColorConvertOp from an array of ICC_Profiles.
161      * The RenderingHints argument may be null.
162      * The sequence of profiles may include profiles that represent color
163      * spaces, profiles that represent effects, etc. If the whole sequence
164      * does not represent a well-defined color conversion, an exception is
165      * thrown.
166      * <p>For BufferedImages, if the ColorSpace
167      * of the source BufferedImage does not match the requirements of the
168      * first profile in the array,
169      * the first conversion is to an appropriate ColorSpace.
170      * If the requirements of the last profile in the array are not met
171      * by the ColorSpace of the destination BufferedImage,
172      * the last conversion is to the destination's ColorSpace.
173      * <p>For Rasters, the number of bands in the source Raster must match
174      * the requirements of the first profile in the array, and the
175      * number of bands in the destination Raster must match the requirements
176      * of the last profile in the array. The array must have at least two
177      * elements or calling the filter method for Rasters will throw an
178      * IllegalArgumentException.
179      * @param profiles the array of <code>ICC_Profile</code> objects
180      * @param hints the <code>RenderingHints</code> object used to control
181      * the color conversion, or <code>null</code>
182      * @exception IllegalArgumentException when the profile sequence does not
183      * specify a well-defined color conversion
184      * @exception NullPointerException if profiles is null
185      */

186     public ColorConvertOp (ICC_Profile[] profiles, RenderingHints JavaDoc hints)
187     {
188         if (profiles == null) {
189             throw new NullPointerException JavaDoc("Profiles cannot be null");
190         }
191         gotProfiles = true;
192         profileList = new ICC_Profile[profiles.length];
193         for (int i1 = 0; i1 < profiles.length; i1++) {
194         profileList[i1] = profiles[i1];
195     }
196         this.hints = hints;
197     }
198
199
200     /**
201      * Returns the array of ICC_Profiles used to construct this ColorConvertOp.
202      * Returns null if the ColorConvertOp was not constructed from such an
203      * array.
204      * @return the array of <code>ICC_Profile</code> objects of this
205      * <code>ColorConvertOp</code>, or <code>null</code> if this
206      * <code>ColorConvertOp</code> was not constructed with an
207      * array of <code>ICC_Profile</code> objects.
208      */

209     public final ICC_Profile[] getICC_Profiles() {
210         if (gotProfiles) {
211             ICC_Profile[] profiles = new ICC_Profile[profileList.length];
212             for (int i1 = 0; i1 < profileList.length; i1++) {
213                 profiles[i1] = profileList[i1];
214             }
215             return profiles;
216         }
217         return null;
218     }
219
220     /**
221      * ColorConverts the source BufferedImage.
222      * If the destination image is null,
223      * a BufferedImage will be created with an appropriate ColorModel.
224      * @param src the source <code>BufferedImage</code> to be converted
225      * @param dest the destination <code>BufferedImage</code>,
226      * or <code>null</code>
227      * @return <code>dest</code> color converted from <code>src</code>
228      * or a new, converted <code>BufferedImage</code>
229      * if <code>dest</code> is <code>null</code>
230      * @exception IllegalArgumentException if dest is null and this op was
231      * constructed using the constructor which takes only a
232      * RenderingHints argument, since the operation is ill defined.
233      */

234     public final BufferedImage JavaDoc filter(BufferedImage JavaDoc src, BufferedImage JavaDoc dest) {
235         ColorSpace srcColorSpace, destColorSpace;
236         BufferedImage JavaDoc savdest = null;
237
238         if (src.getColorModel() instanceof IndexColorModel JavaDoc) {
239             IndexColorModel JavaDoc icm = (IndexColorModel JavaDoc) src.getColorModel();
240             src = icm.convertToIntDiscrete(src.getRaster(), true);
241         }
242         srcColorSpace = src.getColorModel().getColorSpace();
243         if (dest != null) {
244             if (dest.getColorModel() instanceof IndexColorModel JavaDoc) {
245                 savdest = dest;
246                 dest = null;
247                 destColorSpace = null;
248             } else {
249                 destColorSpace = dest.getColorModel().getColorSpace();
250             }
251         } else {
252             destColorSpace = null;
253         }
254
255         if ((CSList != null) ||
256             (!(srcColorSpace instanceof ICC_ColorSpace)) ||
257             ((dest != null) &&
258              (!(destColorSpace instanceof ICC_ColorSpace)))) {
259             /* non-ICC case */
260             dest = nonICCBIFilter(src, srcColorSpace, dest, destColorSpace);
261         } else {
262             dest = ICCBIFilter(src, srcColorSpace, dest, destColorSpace);
263         }
264
265         if (savdest != null) {
266             Graphics2D JavaDoc big = savdest.createGraphics();
267         try {
268             big.drawImage(dest, 0, 0, null);
269         } finally {
270             big.dispose();
271         }
272             return savdest;
273         } else {
274             return dest;
275         }
276     }
277
278     private final BufferedImage JavaDoc ICCBIFilter(BufferedImage JavaDoc src,
279                                             ColorSpace srcColorSpace,
280                                             BufferedImage JavaDoc dest,
281                                             ColorSpace destColorSpace) {
282     int nProfiles = profileList.length;
283     ICC_Profile srcProfile = null, destProfile = null;
284
285         srcProfile = ((ICC_ColorSpace) srcColorSpace).getProfile();
286
287         if (dest == null) { /* last profile in the list defines
288                                       the output color space */

289             if (nProfiles == 0) {
290                 throw new IllegalArgumentException JavaDoc(
291                     "Destination ColorSpace is undefined");
292             }
293             destProfile = profileList [nProfiles - 1];
294             dest = createCompatibleDestImage(src, null);
295         }
296         else {
297             if (src.getHeight() != dest.getHeight() ||
298                 src.getWidth() != dest.getWidth()) {
299                 throw new IllegalArgumentException JavaDoc(
300                     "Width or height of BufferedImages do not match");
301             }
302             destProfile = ((ICC_ColorSpace) destColorSpace).getProfile();
303         }
304
305         /* make a new transform if needed */
306         if ((thisTransform == null) || (thisSrcProfile != srcProfile) ||
307             (thisDestProfile != destProfile) ) {
308             updateBITransform(srcProfile, destProfile);
309         }
310
311         /* color convert the image */
312         thisTransform.colorConvert(src, dest);
313
314         return dest;
315     }
316
317     private void updateBITransform(ICC_Profile srcProfile,
318                                    ICC_Profile destProfile) {
319         ICC_Profile[] theProfiles;
320         int i1, nProfiles, nTransforms, whichTrans, renderState;
321         ICC_Transform[] theTransforms;
322         boolean useSrc = false, useDest = false;
323
324         nProfiles = profileList.length;
325         nTransforms = nProfiles;
326         if ((nProfiles == 0) || (srcProfile != profileList[0])) {
327             nTransforms += 1;
328             useSrc = true;
329         }
330         if ((nProfiles == 0) || (destProfile != profileList[nProfiles - 1]) ||
331             (nTransforms < 2)) {
332             nTransforms += 1;
333             useDest = true;
334         }
335
336         /* make the profile list */
337         theProfiles = new ICC_Profile[nTransforms]; /* the list of profiles
338                                                        for this Op */

339
340         int idx = 0;
341         if (useSrc) {
342             /* insert source as first profile */
343             theProfiles[idx++] = srcProfile;
344         }
345
346         for (i1 = 0; i1 < nProfiles; i1++) {
347                                    /* insert profiles defined in this Op */
348             theProfiles[idx++] = profileList [i1];
349         }
350
351         if (useDest) {
352             /* insert dest as last profile */
353             theProfiles[idx] = destProfile;
354         }
355
356         /* make the transform list */
357         theTransforms = new ICC_Transform [nTransforms];
358
359         /* initialize transform get loop */
360         if (theProfiles[0].getProfileClass() == ICC_Profile.CLASS_OUTPUT) {
361                                         /* if first profile is a printer
362                                            render as colorimetric */

363             renderState = ICC_Profile.icRelativeColorimetric;
364         }
365         else {
366             renderState = ICC_Profile.icPerceptual; /* render any other
367                                                        class perceptually */

368         }
369
370         whichTrans = ICC_Transform.In;
371
372         /* get the transforms from each profile */
373         for (i1 = 0; i1 < nTransforms; i1++) {
374             if (i1 == nTransforms -1) { /* last profile? */
375                 whichTrans = ICC_Transform.Out; /* get output transform */
376             }
377             else { /* check for abstract profile */
378                 if ((whichTrans == ICC_Transform.Simulation) &&
379                     (theProfiles[i1].getProfileClass () ==
380                      ICC_Profile.CLASS_ABSTRACT)) {
381                 renderState = ICC_Profile.icPerceptual;
382                     whichTrans = ICC_Transform.In;
383                 }
384             }
385
386             theTransforms[i1] = new ICC_Transform (theProfiles[i1],
387                                                    renderState, whichTrans);
388
389             /* get this profile's rendering intent to select transform
390                from next profile */

391             renderState = getRenderingIntent(theProfiles[i1]);
392
393             /* "middle" profiles use simulation transform */
394             whichTrans = ICC_Transform.Simulation;
395         }
396
397         /* make the net transform */
398         thisTransform = new ICC_Transform (theTransforms);
399
400         /* update corresponding source and dest profiles */
401         thisSrcProfile = srcProfile;
402         thisDestProfile = destProfile;
403     }
404
405     /**
406      * ColorConverts the image data in the source Raster.
407      * If the destination Raster is null, a new Raster will be created.
408      * The number of bands in the source and destination Rasters must
409      * meet the requirements explained above. The constructor used to
410      * create this ColorConvertOp must have provided enough information
411      * to define both source and destination color spaces. See above.
412      * Otherwise, an exception is thrown.
413      * @param src the source <code>Raster</code> to be converted
414      * @param dest the destination <code>WritableRaster</code>,
415      * or <code>null</code>
416      * @return <code>dest</code> color converted from <code>src</code>
417      * or a new, converted <code>WritableRaster</code>
418      * if <code>dest</code> is <code>null</code>
419      * @exception IllegalArgumentException if the number of source or
420      * destination bands is incorrect, the source or destination
421      * color spaces are undefined, or this op was constructed
422      * with one of the constructors that applies only to
423      * operations on BufferedImages.
424      */

425     public final WritableRaster JavaDoc filter (Raster JavaDoc src, WritableRaster JavaDoc dest) {
426
427         if (CSList != null) {
428             /* non-ICC case */
429             return nonICCRasterFilter(src, dest);
430         }
431         int nProfiles = profileList.length;
432         if (nProfiles < 2) {
433             throw new IllegalArgumentException JavaDoc(
434                 "Source or Destination ColorSpace is undefined");
435         }
436         if (src.getNumBands() != profileList[0].getNumComponents()) {
437             throw new IllegalArgumentException JavaDoc(
438                 "Numbers of source Raster bands and source color space " +
439                 "components do not match");
440         }
441         if (dest == null) {
442             dest = createCompatibleDestRaster(src);
443         }
444         else {
445             if (src.getHeight() != dest.getHeight() ||
446                 src.getWidth() != dest.getWidth()) {
447                 throw new IllegalArgumentException JavaDoc(
448                     "Width or height of Rasters do not match");
449             }
450             if (dest.getNumBands() !=
451                 profileList[nProfiles-1].getNumComponents()) {
452                 throw new IllegalArgumentException JavaDoc(
453                     "Numbers of destination Raster bands and destination " +
454                     "color space components do not match");
455             }
456         }
457
458         /* make a new transform if needed */
459         if (thisRasterTransform == null) {
460             int i1, whichTrans, renderState;
461             ICC_Transform[] theTransforms;
462
463             /* make the transform list */
464             theTransforms = new ICC_Transform [nProfiles];
465
466             /* initialize transform get loop */
467             if (profileList[0].getProfileClass() == ICC_Profile.CLASS_OUTPUT) {
468                                             /* if first profile is a printer
469                                                render as colorimetric */

470                 renderState = ICC_Profile.icRelativeColorimetric;
471             }
472             else {
473                 renderState = ICC_Profile.icPerceptual; /* render any other
474                                                            class perceptually */

475             }
476
477             whichTrans = ICC_Transform.In;
478
479             /* get the transforms from each profile */
480             for (i1 = 0; i1 < nProfiles; i1++) {
481                 if (i1 == nProfiles -1) { /* last profile? */
482                     whichTrans = ICC_Transform.Out; /* get output transform */
483                 }
484                 else { /* check for abstract profile */
485                     if ((whichTrans == ICC_Transform.Simulation) &&
486                         (profileList[i1].getProfileClass () ==
487                          ICC_Profile.CLASS_ABSTRACT)) {
488                     renderState = ICC_Profile.icPerceptual;
489                         whichTrans = ICC_Transform.In;
490                     }
491                 }
492
493                 theTransforms[i1] = new ICC_Transform (profileList[i1],
494                                                        renderState, whichTrans);
495
496                 /* get this profile's rendering intent to select transform
497                    from next profile */

498                 renderState = getRenderingIntent(profileList[i1]);
499
500                 /* "middle" profiles use simulation transform */
501                 whichTrans = ICC_Transform.Simulation;
502             }
503
504             /* make the net transform */
505             thisRasterTransform = new ICC_Transform (theTransforms);
506         }
507
508         int srcTransferType = src.getTransferType();
509         int dstTransferType = dest.getTransferType();
510         if ((srcTransferType == DataBuffer.TYPE_FLOAT) ||
511             (srcTransferType == DataBuffer.TYPE_DOUBLE) ||
512             (dstTransferType == DataBuffer.TYPE_FLOAT) ||
513             (dstTransferType == DataBuffer.TYPE_DOUBLE)) {
514             if (srcMinVals == null) {
515                 getMinMaxValsFromProfiles(profileList[0],
516                                           profileList[nProfiles-1]);
517             }
518             /* color convert the raster */
519             thisRasterTransform.colorConvert(src, dest,
520                                              srcMinVals, srcMaxVals,
521                                              dstMinVals, dstMaxVals);
522         } else {
523             /* color convert the raster */
524             thisRasterTransform.colorConvert(src, dest);
525         }
526
527
528         return dest;
529     }
530
531     /**
532      * Returns the bounding box of the destination, given this source.
533      * Note that this will be the same as the the bounding box of the
534      * source.
535      * @param src the source <code>BufferedImage</code>
536      * @return a <code>Rectangle2D</code> that is the bounding box
537      * of the destination, given the specified <code>src</code>
538      */

539     public final Rectangle2D JavaDoc getBounds2D (BufferedImage JavaDoc src) {
540         return getBounds2D(src.getRaster());
541     }
542
543     /**
544      * Returns the bounding box of the destination, given this source.
545      * Note that this will be the same as the the bounding box of the
546      * source.
547      * @param src the source <code>Raster</code>
548      * @return a <code>Rectangle2D</code> that is the bounding box
549      * of the destination, given the specified <code>src</code>
550      */

551     public final Rectangle2D JavaDoc getBounds2D (Raster JavaDoc src) {
552         /* return new Rectangle (src.getXOffset(),
553                               src.getYOffset(),
554                               src.getWidth(), src.getHeight()); */

555         return src.getBounds();
556     }
557
558     /**
559      * Creates a zeroed destination image with the correct size and number of
560      * bands, given this source.
561      * @param src Source image for the filter operation.
562      * @param destCM ColorModel of the destination. If null, an
563      * appropriate ColorModel will be used.
564      * @return a <code>BufferedImage</code> with the correct size and
565      * number of bands from the specified <code>src</code>.
566      * @throws IllegalArgumentException if <code>destCM</code> is
567      * <code>null</code> and this <code>ColorConvertOp</code> was
568      * created without any <code>ICC_Profile</code> or
569      * <code>ColorSpace</code> defined for the destination
570      */

571     public BufferedImage JavaDoc createCompatibleDestImage (BufferedImage JavaDoc src,
572                                                     ColorModel JavaDoc destCM) {
573         ColorSpace cs = null;;
574         if (destCM == null) {
575             if (CSList == null) {
576                 /* ICC case */
577                 int nProfiles = profileList.length;
578                 if (nProfiles == 0) {
579                     throw new IllegalArgumentException JavaDoc(
580                         "Destination ColorSpace is undefined");
581                 }
582                 ICC_Profile destProfile = profileList[nProfiles - 1];
583                 cs = new ICC_ColorSpace(destProfile);
584             } else {
585                 /* non-ICC case */
586                 int nSpaces = CSList.length;
587                 cs = CSList[nSpaces - 1];
588             }
589         }
590         return createCompatibleDestImage(src, destCM, cs);
591     }
592
593     private BufferedImage JavaDoc createCompatibleDestImage(BufferedImage JavaDoc src,
594                                                     ColorModel JavaDoc destCM,
595                                                     ColorSpace destCS) {
596         BufferedImage JavaDoc image;
597         if (destCM == null) {
598             ColorModel JavaDoc srcCM = src.getColorModel();
599             int nbands = destCS.getNumComponents();
600             boolean hasAlpha = srcCM.hasAlpha();
601             if (hasAlpha) {
602                nbands += 1;
603             }
604             int[] nbits = new int[nbands];
605             for (int i = 0; i < nbands; i++) {
606                 nbits[i] = 8;
607             }
608             destCM = new ComponentColorModel JavaDoc(destCS, nbits, hasAlpha,
609                                              srcCM.isAlphaPremultiplied(),
610                                              srcCM.getTransparency(),
611                                              DataBuffer.TYPE_BYTE);
612         }
613         int w = src.getWidth();
614         int h = src.getHeight();
615         image = new BufferedImage JavaDoc(destCM,
616                                   destCM.createCompatibleWritableRaster(w, h),
617                                   destCM.isAlphaPremultiplied(), null);
618         return image;
619     }
620
621
622     /**
623      * Creates a zeroed destination Raster with the correct size and number of
624      * bands, given this source.
625      * @param src the specified <code>Raster</code>
626      * @return a <code>WritableRaster</code> with the correct size and number
627      * of bands from the specified <code>src</code>
628      * @throws IllegalArgumentException if this <code>ColorConvertOp</code>
629      * was created without sufficient information to define the
630      * <code>dst</code> and <code>src</code> color spaces
631      */

632     public WritableRaster JavaDoc createCompatibleDestRaster (Raster JavaDoc src) {
633         int ncomponents;
634
635         if (CSList != null) {
636             /* non-ICC case */
637             if (CSList.length != 2) {
638                 throw new IllegalArgumentException JavaDoc(
639                     "Destination ColorSpace is undefined");
640             }
641             ncomponents = CSList[1].getNumComponents();
642         } else {
643             /* ICC case */
644             int nProfiles = profileList.length;
645             if (nProfiles < 2) {
646                 throw new IllegalArgumentException JavaDoc(
647                     "Destination ColorSpace is undefined");
648             }
649             ncomponents = profileList[nProfiles-1].getNumComponents();
650         }
651
652         WritableRaster JavaDoc dest =
653             Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE,
654                                   src.getWidth(),
655                                   src.getHeight(),
656                                   ncomponents,
657                                   new Point JavaDoc(src.getMinX(), src.getMinY()));
658         return dest;
659     }
660
661     /**
662      * Returns the location of the destination point given a
663      * point in the source. If <code>dstPt</code> is non-null,
664      * it will be used to hold the return value. Note that
665      * for this class, the destination point will be the same
666      * as the source point.
667      * @param srcPt the specified source <code>Point2D</code>
668      * @param dstPt the destination <code>Point2D</code>
669      * @return <code>dstPt</code> after setting its location to be
670      * the same as <code>srcPt</code>
671      */

672     public final Point2D JavaDoc getPoint2D (Point2D JavaDoc srcPt, Point2D JavaDoc dstPt) {
673         if (dstPt == null) {
674             dstPt = new Point2D.Float JavaDoc();
675         }
676         dstPt.setLocation(srcPt.getX(), srcPt.getY());
677
678         return dstPt;
679     }
680
681
682     /**
683      * Returns the RenderingIntent from the specified ICC Profile.
684      */

685     private int getRenderingIntent (ICC_Profile profile) {
686         byte[] header = profile.getData(ICC_Profile.icSigHead);
687         int index = ICC_Profile.icHdrRenderingIntent;
688         return (((header[index] & 0xff) << 24) |
689                 ((header[index+1] & 0xff) << 16) |
690                 ((header[index+2] & 0xff) << 8) |
691                  (header[index+3] & 0xff));
692     }
693
694     /**
695      * Returns the rendering hints used by this op.
696      * @return the <code>RenderingHints</code> object of this
697      * <code>ColorConvertOp</code>
698      */

699     public final RenderingHints JavaDoc getRenderingHints() {
700         return hints;
701     }
702
703     private final BufferedImage JavaDoc nonICCBIFilter(BufferedImage JavaDoc src,
704                                                ColorSpace srcColorSpace,
705                                                BufferedImage JavaDoc dst,
706                                                ColorSpace dstColorSpace) {
707
708         int w = src.getWidth();
709         int h = src.getHeight();
710         ICC_ColorSpace ciespace =
711             (ICC_ColorSpace) ColorSpace.getInstance(ColorSpace.CS_CIEXYZ);
712         if (dst == null) {
713             dst = createCompatibleDestImage(src, null);
714             dstColorSpace = dst.getColorModel().getColorSpace();
715         } else {
716             if ((h != dst.getHeight()) || (w != dst.getWidth())) {
717                 throw new IllegalArgumentException JavaDoc(
718                     "Width or height of BufferedImages do not match");
719             }
720         }
721         Raster JavaDoc srcRas = src.getRaster();
722         WritableRaster JavaDoc dstRas = dst.getRaster();
723         ColorModel JavaDoc srcCM = src.getColorModel();
724         ColorModel JavaDoc dstCM = dst.getColorModel();
725         int srcNumComp = srcCM.getNumColorComponents();
726         int dstNumComp = dstCM.getNumColorComponents();
727         boolean dstHasAlpha = dstCM.hasAlpha();
728         boolean needSrcAlpha = srcCM.hasAlpha() && dstHasAlpha;
729         ColorSpace[] list;
730         if ((CSList == null) && (profileList.length != 0)) {
731             /* possible non-ICC src, some profiles, possible non-ICC dst */
732             boolean nonICCSrc, nonICCDst;
733             ICC_Profile srcProfile, dstProfile;
734             if (!(srcColorSpace instanceof ICC_ColorSpace)) {
735                 nonICCSrc = true;
736                 srcProfile = ciespace.getProfile();
737             } else {
738                 nonICCSrc = false;
739                 srcProfile = ((ICC_ColorSpace) srcColorSpace).getProfile();
740             }
741             if (!(dstColorSpace instanceof ICC_ColorSpace)) {
742                 nonICCDst = true;
743                 dstProfile = ciespace.getProfile();
744             } else {
745                 nonICCDst = false;
746                 dstProfile = ((ICC_ColorSpace) dstColorSpace).getProfile();
747             }
748             /* make a new transform if needed */
749             if ((thisTransform == null) || (thisSrcProfile != srcProfile) ||
750                 (thisDestProfile != dstProfile) ) {
751                 updateBITransform(srcProfile, dstProfile);
752             }
753             // process per scanline
754
float maxNum = 65535.0f; // use 16-bit precision in CMM
755
ColorSpace cs;
756             int iccSrcNumComp;
757             if (nonICCSrc) {
758                 cs = ciespace;
759                 iccSrcNumComp = 3;
760             } else {
761                 cs = srcColorSpace;
762                 iccSrcNumComp = srcNumComp;
763             }
764             float[] srcMinVal = new float[iccSrcNumComp];
765             float[] srcInvDiffMinMax = new float[iccSrcNumComp];
766             for (int i = 0; i < srcNumComp; i++) {
767                 srcMinVal[i] = cs.getMinValue(i);
768                 srcInvDiffMinMax[i] = maxNum / (cs.getMaxValue(i) - srcMinVal[i]);
769             }
770             int iccDstNumComp;
771             if (nonICCDst) {
772                 cs = ciespace;
773                 iccDstNumComp = 3;
774             } else {
775                 cs = dstColorSpace;
776                 iccDstNumComp = dstNumComp;
777             }
778             float[] dstMinVal = new float[iccDstNumComp];
779             float[] dstDiffMinMax = new float[iccDstNumComp];
780             for (int i = 0; i < dstNumComp; i++) {
781                 dstMinVal[i] = cs.getMinValue(i);
782                 dstDiffMinMax[i] = (cs.getMaxValue(i) - dstMinVal[i]) / maxNum;
783             }
784             float[] dstColor;
785             if (dstHasAlpha) {
786                 int size = ((dstNumComp + 1) > 3) ? (dstNumComp + 1) : 3;
787                 dstColor = new float[size];
788             } else {
789                 int size = (dstNumComp > 3) ? dstNumComp : 3;
790                 dstColor = new float[size];
791             }
792             short[] srcLine = new short[w * iccSrcNumComp];
793             short[] dstLine = new short[w * iccDstNumComp];
794             Object JavaDoc pixel;
795             float[] color;
796             float[] alpha = null;
797             if (needSrcAlpha) {
798                 alpha = new float[w];
799             }
800             int idx;
801             // process each scanline
802
for (int y = 0; y < h; y++) {
803                 // convert src scanline
804
pixel = null;
805                 color = null;
806                 idx = 0;
807                 for (int x = 0; x < w; x++) {
808                     pixel = srcRas.getDataElements(x, y, pixel);
809                     color = srcCM.getNormalizedComponents(pixel, color, 0);
810                     if (needSrcAlpha) {
811                         alpha[x] = color[srcNumComp];
812                     }
813                     if (nonICCSrc) {
814                         color = srcColorSpace.toCIEXYZ(color);
815                     }
816                     for (int i = 0; i < iccSrcNumComp; i++) {
817                         srcLine[idx++] = (short)
818                             ((color[i] - srcMinVal[i]) * srcInvDiffMinMax[i] +
819                              0.5f);
820                     }
821                 }
822                 // color convert srcLine to dstLine
823
thisTransform.colorConvert(srcLine, dstLine);
824                 // convert dst scanline
825
pixel = null;
826                 idx = 0;
827                 for (int x = 0; x < w; x++) {
828                     for (int i = 0; i < iccDstNumComp; i++) {
829                         dstColor[i] = ((float) (dstLine[idx++] & 0xffff)) *
830                                       dstDiffMinMax[i] + dstMinVal[i];
831                     }
832                     if (nonICCDst) {
833                         color = srcColorSpace.fromCIEXYZ(dstColor);
834                         for (int i = 0; i < dstNumComp; i++) {
835                             dstColor[i] = color[i];
836                         }
837                     }
838                     if (needSrcAlpha) {
839                         dstColor[dstNumComp] = alpha[x];
840                     } else if (dstHasAlpha) {
841                         dstColor[dstNumComp] = 1.0f;
842                     }
843                     pixel = dstCM.getDataElements(dstColor, 0, pixel);
844                     dstRas.setDataElements(x, y, pixel);
845                 }
846             }
847         } else {
848             /* possible non-ICC src, possible CSList, possible non-ICC dst */
849             // process per pixel
850
int numCS;
851             if (CSList == null) {
852                 numCS = 0;
853             } else {
854                 numCS = CSList.length;
855             }
856             float[] dstColor;
857             if (dstHasAlpha) {
858                 dstColor = new float[dstNumComp + 1];
859             } else {
860                 dstColor = new float[dstNumComp];
861             }
862             Object JavaDoc spixel = null;
863             Object JavaDoc dpixel = null;
864             float[] color = null;
865             float[] tmpColor;
866             // process each pixel
867
for (int y = 0; y < h; y++) {
868                 for (int x = 0; x < w; x++) {
869                     spixel = srcRas.getDataElements(x, y, spixel);
870                     color = srcCM.getNormalizedComponents(spixel, color, 0);
871                     tmpColor = srcColorSpace.toCIEXYZ(color);
872                     for (int i = 0; i < numCS; i++) {
873                         tmpColor = CSList[i].fromCIEXYZ(tmpColor);
874                         tmpColor = CSList[i].toCIEXYZ(tmpColor);
875                     }
876                     tmpColor = dstColorSpace.fromCIEXYZ(tmpColor);
877                     for (int i = 0; i < dstNumComp; i++) {
878                         dstColor[i] = tmpColor[i];
879                     }
880                     if (needSrcAlpha) {
881                         dstColor[dstNumComp] = color[srcNumComp];
882                     } else if (dstHasAlpha) {
883                         dstColor[dstNumComp] = 1.0f;
884                     }
885                     dpixel = dstCM.getDataElements(dstColor, 0, dpixel);
886                     dstRas.setDataElements(x, y, dpixel);
887                     
888                 }
889             }
890         }
891
892         return dst;
893     }
894
895     /* color convert a Raster - handles byte, ushort, int, short, float,
896        or double transferTypes */

897     private final WritableRaster JavaDoc nonICCRasterFilter(Raster JavaDoc src,
898                                                     WritableRaster JavaDoc dst) {
899
900         if (CSList.length != 2) {
901             throw new IllegalArgumentException JavaDoc(
902                 "Destination ColorSpace is undefined");
903         }
904         if (src.getNumBands() != CSList[0].getNumComponents()) {
905             throw new IllegalArgumentException JavaDoc(
906                 "Numbers of source Raster bands and source color space " +
907                 "components do not match");
908         }
909         if (dst == null) {
910             dst = createCompatibleDestRaster(src);
911         } else {
912             if (src.getHeight() != dst.getHeight() ||
913                 src.getWidth() != dst.getWidth()) {
914                 throw new IllegalArgumentException JavaDoc(
915                     "Width or height of Rasters do not match");
916             }
917             if (dst.getNumBands() != CSList[1].getNumComponents()) {
918                 throw new IllegalArgumentException JavaDoc(
919                     "Numbers of destination Raster bands and destination " +
920                     "color space components do not match");
921             }
922         }
923
924         if (srcMinVals == null) {
925             getMinMaxValsFromColorSpaces(CSList[0], CSList[1]);
926         }
927
928         SampleModel JavaDoc srcSM = src.getSampleModel();
929         SampleModel JavaDoc dstSM = dst.getSampleModel();
930         boolean srcIsFloat, dstIsFloat;
931         int srcTransferType = src.getTransferType();
932         int dstTransferType = dst.getTransferType();
933         if ((srcTransferType == DataBuffer.TYPE_FLOAT) ||
934             (srcTransferType == DataBuffer.TYPE_DOUBLE)) {
935             srcIsFloat = true;
936         } else {
937             srcIsFloat = false;
938         }
939         if ((dstTransferType == DataBuffer.TYPE_FLOAT) ||
940             (dstTransferType == DataBuffer.TYPE_DOUBLE)) {
941             dstIsFloat = true;
942         } else {
943             dstIsFloat = false;
944         }
945         int w = src.getWidth();
946         int h = src.getHeight();
947         int srcNumBands = src.getNumBands();
948         int dstNumBands = dst.getNumBands();
949         float[] srcScaleFactor = null;
950         float[] dstScaleFactor = null;
951         if (!srcIsFloat) {
952             srcScaleFactor = new float[srcNumBands];
953             for (int i = 0; i < srcNumBands; i++) {
954                 if (srcTransferType == DataBuffer.TYPE_SHORT) {
955                     srcScaleFactor[i] = (srcMaxVals[i] - srcMinVals[i]) /
956                                         32767.0f;
957                 } else {
958                     srcScaleFactor[i] = (srcMaxVals[i] - srcMinVals[i]) /
959                         ((float) ((1 << srcSM.getSampleSize(i)) - 1));
960                 }
961             }
962         }
963         if (!dstIsFloat) {
964             dstScaleFactor = new float[dstNumBands];
965             for (int i = 0; i < dstNumBands; i++) {
966                 if (dstTransferType == DataBuffer.TYPE_SHORT) {
967                     dstScaleFactor[i] = 32767.0f /
968                                         (dstMaxVals[i] - dstMinVals[i]);
969                 } else {
970                     dstScaleFactor[i] =
971                         ((float) ((1 << dstSM.getSampleSize(i)) - 1)) /
972                         (dstMaxVals[i] - dstMinVals[i]);
973                 }
974             }
975         }
976         int ys = src.getMinY();
977         int yd = dst.getMinY();
978         int xs, xd;
979         float sample;
980         float[] color = new float[srcNumBands];
981         float[] tmpColor;
982         ColorSpace srcColorSpace = CSList[0];
983         ColorSpace dstColorSpace = CSList[1];
984         // process each pixel
985
for (int y = 0; y < h; y++, ys++, yd++) {
986             // get src scanline
987
xs = src.getMinX();
988             xd = dst.getMinX();
989             for (int x = 0; x < w; x++, xs++, xd++) {
990                 for (int i = 0; i < srcNumBands; i++) {
991                     sample = src.getSampleFloat(xs, ys, i);
992                     if (!srcIsFloat) {
993                         sample = sample * srcScaleFactor[i] + srcMinVals[i];
994                     }
995                     color[i] = sample;
996                 }
997                 tmpColor = srcColorSpace.toCIEXYZ(color);
998                 tmpColor = dstColorSpace.fromCIEXYZ(tmpColor);
999                 for (int i = 0; i < dstNumBands; i++) {
1000                    sample = tmpColor[i];
1001                    if (!dstIsFloat) {
1002                        sample = (sample - dstMinVals[i]) * dstScaleFactor[i];
1003                    }
1004                    dst.setSample(xd, yd, i, sample);
1005                }
1006            }
1007        }
1008        return dst;
1009    }
1010
1011    private void getMinMaxValsFromProfiles(ICC_Profile srcProfile,
1012                                           ICC_Profile dstProfile) {
1013        int type = srcProfile.getColorSpaceType();
1014        int nc = srcProfile.getNumComponents();
1015        srcMinVals = new float[nc];
1016        srcMaxVals = new float[nc];
1017        setMinMax(type, nc, srcMinVals, srcMaxVals);
1018        type = dstProfile.getColorSpaceType();
1019        nc = dstProfile.getNumComponents();
1020        dstMinVals = new float[nc];
1021        dstMaxVals = new float[nc];
1022        setMinMax(type, nc, dstMinVals, dstMaxVals);
1023    }
1024
1025    private void setMinMax(int type, int nc, float[] minVals, float[] maxVals) {
1026        if (type == ColorSpace.TYPE_Lab) {
1027            minVals[0] = 0.0f; // L
1028
maxVals[0] = 100.0f;
1029            minVals[1] = -128.0f; // a
1030
maxVals[1] = 127.0f;
1031            minVals[2] = -128.0f; // b
1032
maxVals[2] = 127.0f;
1033        } else if (type == ColorSpace.TYPE_XYZ) {
1034            minVals[0] = minVals[1] = minVals[2] = 0.0f; // X, Y, Z
1035
maxVals[0] = maxVals[1] = maxVals[2] = 1.0f + (32767.0f/ 32768.0f);
1036        } else {
1037            for (int i = 0; i < nc; i++) {
1038                minVals[i] = 0.0f;
1039                maxVals[i] = 1.0f;
1040            }
1041        }
1042    }
1043
1044    private void getMinMaxValsFromColorSpaces(ColorSpace srcCspace,
1045                                              ColorSpace dstCspace) {
1046        int nc = srcCspace.getNumComponents();
1047        srcMinVals = new float[nc];
1048        srcMaxVals = new float[nc];
1049        for (int i = 0; i < nc; i++) {
1050            srcMinVals[i] = srcCspace.getMinValue(i);
1051            srcMaxVals[i] = srcCspace.getMaxValue(i);
1052        }
1053        nc = dstCspace.getNumComponents();
1054        dstMinVals = new float[nc];
1055        dstMaxVals = new float[nc];
1056        for (int i = 0; i < nc; i++) {
1057            dstMinVals[i] = dstCspace.getMinValue(i);
1058            dstMaxVals[i] = dstCspace.getMaxValue(i);
1059        }
1060    }
1061
1062}
1063
Popular Tags