1 /*
2 * Portions Copyright 1997-2007 Sun Microsystems, Inc. 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. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25
26 /**********************************************************************
27 **********************************************************************
28 **********************************************************************
29 *** COPYRIGHT (c) Eastman Kodak Company, 1997 ***
30 *** As an unpublished work pursuant to Title 17 of the United ***
31 *** States Code. All rights reserved. ***
32 **********************************************************************
33 **********************************************************************
34 **********************************************************************/
35
36 package java.awt.image;
37
38 import java.awt.Point;
39 import java.awt.Graphics2D;
40 import java.awt.color;
41 import sun.java2d.cmm.ColorTransform;
42 import sun.java2d.cmm.CMSManager;
43 import sun.java2d.cmm.ProfileDeferralMgr;
44 import sun.java2d.cmm.PCMM;
45 import java.awt.geom.Rectangle2D;
46 import java.awt.geom.Point2D;
47 import java.awt.RenderingHints;
48
49 /**
50 * This class performs a pixel-by-pixel color conversion of the data in
51 * the source image. The resulting color values are scaled to the precision
52 * of the destination image. Color conversion can be specified
53 * via an array of ColorSpace objects or an array of ICC_Profile objects.
54 * <p>
55 * If the source is a BufferedImage with premultiplied alpha, the
56 * color components are divided by the alpha component before color conversion.
57 * If the destination is a BufferedImage with premultiplied alpha, the
58 * color components are multiplied by the alpha component after conversion.
59 * Rasters are treated as having no alpha channel, i.e. all bands are
60 * color bands.
61 * <p>
62 * If a RenderingHints object is specified in the constructor, the
63 * color rendering hint and the dithering hint may be used to control
64 * color conversion.
65 * <p>
66 * Note that Source and Destination may be the same object.
67 * <p>
68 * @see java.awt.RenderingHints#KEY_COLOR_RENDERING
69 * @see java.awt.RenderingHints#KEY_DITHERING
70 */
71 public 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</code> object used to control
96 * the color conversion, or <code>null</code>
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</code> or an
115 * intermediate <code>ColorSpace</code>
116 * @param hints the <code>RenderingHints</code> object used to control
117 * the color conversion, or <code>null</code>
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</code>
150 * @param dstCspace the destination <code>ColorSpace</code>
151 * @param hints the <code>RenderingHints</code> object used to control
152 * the color conversion, or <code>null</code>
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</code> objects
200 * @param hints the <code>RenderingHints</code> object used to control
201 * the color conversion, or <code>null</code>
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</code> objects of this
225 * <code>ColorConvertOp</code>, or <code>null</code> if this
226 * <code>ColorConvertOp</code> was not constructed with an
227 * array of <code>ICC_Profile</code> 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</code> to be converted
245 * @param dest the destination <code>BufferedImage</code>,
246 * or <code>null</code>
247 * @return <code>dest</code> color converted from <code>src</code>
248 * or a new, converted <code>BufferedImage</code>
249 * if <code>dest</code> is <code>null</code>
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</code> to be converted
459 * @param dest the destination <code>WritableRaster</code>,
460 * or <code>null</code>
461 * @return <code>dest</code> color converted from <code>src</code>
462 * or a new, converted <code>WritableRaster</code>
463 * if <code>dest</code> is <code>null</code>
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 the bounding box of the
581 * source.
582 * @param src the source <code>BufferedImage</code>
583 * @return a <code>Rectangle2D</code> that is the bounding box
584 * of the destination, given the specified <code>src</code>
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 the bounding box of the
593 * source.
594 * @param src the source <code>Raster</code>
595 * @return a <code>Rectangle2D</code> that is the bounding box
596 * of the destination, given the specified <code>src</code>
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</code> with the correct size and
612 * number of bands from the specified <code>src</code>.
613 * @throws IllegalArgumentException if <code>destCM</code> is
614 * <code>null</code> and this <code>ColorConvertOp</code> was
615 * created without any <code>ICC_Profile</code> or
616 * <code>ColorSpace</code> 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</code>
673 * @return a <code>WritableRaster</code> with the correct size and number
674 * of bands from the specified <code>src</code>
675 * @throws IllegalArgumentException if this <code>ColorConvertOp</code>
676 * was created without sufficient information to define the
677 * <code>dst</code> and <code>src</code> 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</code> 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</code>
715 * @param dstPt the destination <code>Point2D</code>
716 * @return <code>dstPt</code> after setting its location to be
717 * the same as <code>srcPt</code>
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 return (((header[index] & 0xff) << 24) |
736 ((header[index+1] & 0xff) << 16) |
737 ((header[index+2] & 0xff) << 8) |
738 (header[index+3] & 0xff));
739 }
740
741 /**
742 * Returns the rendering hints used by this op.
743 * @return the <code>RenderingHints</code> object of this
744 * <code>ColorConvertOp</code>
745 */
746 public final RenderingHints getRenderingHints() {
747 return hints;
748 }
749
750 private final BufferedImage nonICCBIFilter(BufferedImage src,
751 ColorSpace srcColorSpace,
752 BufferedImage dst,
753 ColorSpace dstColorSpace) {
754
755 int w = src.getWidth();
756 int h = src.getHeight();
757 ICC_ColorSpace ciespace =
758 (ICC_ColorSpace) ColorSpace.getInstance(ColorSpace.CS_CIEXYZ);
759 if (dst == null) {
760 dst = createCompatibleDestImage(src, null);
761 dstColorSpace = dst.getColorModel().getColorSpace();
762 } else {
763 if ((h != dst.getHeight()) || (w != dst.getWidth())) {
764 throw new IllegalArgumentException(
765 "Width or height of BufferedImages do not match");
766 }
767 }
768 Raster srcRas = src.getRaster();
769 WritableRaster dstRas = dst.getRaster();
770 ColorModel srcCM = src.getColorModel();
771 ColorModel dstCM = dst.getColorModel();
772 int srcNumComp = srcCM.getNumColorComponents();
773 int dstNumComp = dstCM.getNumColorComponents();
774 boolean dstHasAlpha = dstCM.hasAlpha();
775 boolean needSrcAlpha = srcCM.hasAlpha() && dstHasAlpha;
776 ColorSpace[] list;
777 if ((CSList == null) && (profileList.length != 0)) {
778 /* possible non-ICC src, some profiles, possible non-ICC dst */
779 boolean nonICCSrc, nonICCDst;
780 ICC_Profile srcProfile, dstProfile;
781 if (!(srcColorSpace instanceof ICC_ColorSpace)) {
782 nonICCSrc = true;
783 srcProfile = ciespace.getProfile();
784 } else {
785 nonICCSrc = false;
786 srcProfile = ((ICC_ColorSpace) srcColorSpace).getProfile();
787 }
788 if (!(dstColorSpace instanceof ICC_ColorSpace)) {
789 nonICCDst = true;
790 dstProfile = ciespace.getProfile();
791 } else {
792 nonICCDst = false;
793 dstProfile = ((ICC_ColorSpace) dstColorSpace).getProfile();
794 }
795 /* make a new transform if needed */
796 if ((thisTransform == null) || (thisSrcProfile != srcProfile) ||
797 (thisDestProfile != dstProfile) ) {
798 updateBITransform(srcProfile, dstProfile);
799 }
800 // process per scanline
801 float maxNum = 65535.0f; // use 16-bit precision in CMM
802 ColorSpace cs;
803 int iccSrcNumComp;
804 if (nonICCSrc) {
805 cs = ciespace;
806 iccSrcNumComp = 3;
807 } else {
808 cs = srcColorSpace;
809 iccSrcNumComp = srcNumComp;
810 }
811 float[] srcMinVal = new float[iccSrcNumComp];
812 float[] srcInvDiffMinMax = new float[iccSrcNumComp];
813 for (int i = 0; i < srcNumComp; i++) {
814 srcMinVal[i] = cs.getMinValue(i);
815 srcInvDiffMinMax[i] = maxNum / (cs.getMaxValue(i) - srcMinVal[i]);
816 }
817 int iccDstNumComp;
818 if (nonICCDst) {
819 cs = ciespace;
820 iccDstNumComp = 3;
821 } else {
822 cs = dstColorSpace;
823 iccDstNumComp = dstNumComp;
824 }
825 float[] dstMinVal = new float[iccDstNumComp];
826 float[] dstDiffMinMax = new float[iccDstNumComp];
827 for (int i = 0; i < dstNumComp; i++) {
828 dstMinVal[i] = cs.getMinValue(i);
829 dstDiffMinMax[i] = (cs.getMaxValue(i) - dstMinVal[i]) / maxNum;
830 }
831 float[] dstColor;
832 if (dstHasAlpha) {
833 int size = ((dstNumComp + 1) > 3) ? (dstNumComp + 1) : 3;
834 dstColor = new float[size];
835 } else {
836 int size = (dstNumComp > 3) ? dstNumComp : 3;
837 dstColor = new float[size];
838 }
839 short[] srcLine = new short[w * iccSrcNumComp];
840 short[] dstLine = new short[w * iccDstNumComp];
841 Object pixel;
842 float[] color;
843 float[] alpha = null;
844 if (needSrcAlpha) {
845 alpha = new float[w];
846 }
847 int idx;
848 // process each scanline
849 for (int y = 0; y < h; y++) {
850 // convert src scanline
851 pixel = null;
852 color = null;
853 idx = 0;
854 for (int x = 0; x < w; x++) {
855 pixel = srcRas.getDataElements(x, y, pixel);
856 color = srcCM.getNormalizedComponents(pixel, color, 0);
857 if (needSrcAlpha) {
858 alpha[x] = color[srcNumComp];
859 }
860 if (nonICCSrc) {
861 color = srcColorSpace.toCIEXYZ(color);
862 }
863 for (int i = 0; i < iccSrcNumComp; i++) {
864 srcLine[idx++] = (short)
865 ((color[i] - srcMinVal[i]) * srcInvDiffMinMax[i] +
866 0.5f);
867 }
868 }
869 // color convert srcLine to dstLine
870 thisTransform.colorConvert(srcLine, dstLine);
871 // convert dst scanline
872 pixel = null;
873 idx = 0;
874 for (int x = 0; x < w; x++) {
875 for (int i = 0; i < iccDstNumComp; i++) {
876 dstColor[i] = ((float) (dstLine[idx++] & 0xffff)) *
877 dstDiffMinMax[i] + dstMinVal[i];
878 }
879 if (nonICCDst) {
880 color = srcColorSpace.fromCIEXYZ(dstColor);
881 for (int i = 0; i < dstNumComp; i++) {
882 dstColor[i] = color[i];
883 }
884 }
885 if (needSrcAlpha) {
886 dstColor[dstNumComp] = alpha[x];
887 } else if (dstHasAlpha) {
888 dstColor[dstNumComp] = 1.0f;
889 }
890 pixel = dstCM.getDataElements(dstColor, 0, pixel);
891 dstRas.setDataElements(x, y, pixel);
892 }
893 }
894 } else {
895 /* possible non-ICC src, possible CSList, possible non-ICC dst */
896 // process per pixel
897 int numCS;
898 if (CSList == null) {
899 numCS = 0;
900 } else {
901 numCS = CSList.length;
902 }
903 float[] dstColor;
904 if (dstHasAlpha) {
905 dstColor = new float[dstNumComp + 1];
906 } else {
907 dstColor = new float[dstNumComp];
908 }
909 Object spixel = null;
910 Object dpixel = null;
911 float[] color = null;
912 float[] tmpColor;
913 // process each pixel
914 for (int y = 0; y < h; y++) {
915 for (int x = 0; x < w; x++) {
916 spixel = srcRas.getDataElements(x, y, spixel);
917 color = srcCM.getNormalizedComponents(spixel, color, 0);
918 tmpColor = srcColorSpace.toCIEXYZ(color);
919 for (int i = 0; i < numCS; i++) {
920 tmpColor = CSList[i].fromCIEXYZ(tmpColor);
921 tmpColor = CSList[i].toCIEXYZ(tmpColor);
922 }
923 tmpColor = dstColorSpace.fromCIEXYZ(tmpColor);
924 for (int i = 0; i < dstNumComp; i++) {
925 dstColor[i] = tmpColor[i];
926 }
927 if (needSrcAlpha) {
928 dstColor[dstNumComp] = color[srcNumComp];
929 } else if (dstHasAlpha) {
930 dstColor[dstNumComp] = 1.0f;
931 }
932 dpixel = dstCM.getDataElements(dstColor, 0, dpixel);
933 dstRas.setDataElements(x, y, dpixel);
934
935 }
936 }
937 }
938
939 return dst;
940 }
941
942 /* color convert a Raster - handles byte, ushort, int, short, float,
943 or double transferTypes */
944 private final WritableRaster nonICCRasterFilter(Raster src,
945 WritableRaster dst) {
946
947 if (CSList.length != 2) {
948 throw new IllegalArgumentException(
949 "Destination ColorSpace is undefined");
950 }
951 if (src.getNumBands() != CSList[0].getNumComponents()) {
952 throw new IllegalArgumentException(
953 "Numbers of source Raster bands and source color space " +
954 "components do not match");
955 }
956 if (dst == null) {
957 dst = createCompatibleDestRaster(src);
958 } else {
959 if (src.getHeight() != dst.getHeight() ||
960 src.getWidth() != dst.getWidth()) {
961 throw new IllegalArgumentException(
962 "Width or height of Rasters do not match");
963 }
964 if (dst.getNumBands() != CSList[1].getNumComponents()) {
965 throw new IllegalArgumentException(
966 "Numbers of destination Raster bands and destination " +
967 "color space components do not match");
968 }
969 }
970
971 if (srcMinVals == null) {
972 getMinMaxValsFromColorSpaces(CSList[0], CSList[1]);
973 }
974
975 SampleModel srcSM = src.getSampleModel();
976 SampleModel dstSM = dst.getSampleModel();
977 boolean srcIsFloat, dstIsFloat;
978 int srcTransferType = src.getTransferType();
979 int dstTransferType = dst.getTransferType();
980 if ((srcTransferType == DataBuffer.TYPE_FLOAT) ||
981 (srcTransferType == DataBuffer.TYPE_DOUBLE)) {
982 srcIsFloat = true;
983 } else {
984 srcIsFloat = false;
985 }
986 if ((dstTransferType == DataBuffer.TYPE_FLOAT) ||
987 (dstTransferType == DataBuffer.TYPE_DOUBLE)) {
988 dstIsFloat = true;
989 } else {
990 dstIsFloat = false;
991 }
992 int w = src.getWidth();
993 int h = src.getHeight();
994 int srcNumBands = src.getNumBands();
995 int dstNumBands = dst.getNumBands();
996 float[] srcScaleFactor = null;
997 float[] dstScaleFactor = null;
998 if (!srcIsFloat) {
999 srcScaleFactor = new float[srcNumBands];
1000 for (int i = 0; i < srcNumBands; i++) {
1001 if (srcTransferType == DataBuffer.TYPE_SHORT) {
1002 srcScaleFactor[i] = (srcMaxVals[i] - srcMinVals[i]) /
1003 32767.0f;
1004 } else {
1005 srcScaleFactor[i] = (srcMaxVals[i] - srcMinVals[i]) /
1006 ((float) ((1 << srcSM.getSampleSize(i)) - 1));
1007 }
1008 }
1009 }
1010 if (!dstIsFloat) {
1011 dstScaleFactor = new float[dstNumBands];
1012 for (int i = 0; i < dstNumBands; i++) {
1013 if (dstTransferType == DataBuffer.TYPE_SHORT) {
1014 dstScaleFactor[i] = 32767.0f /
1015 (dstMaxVals[i] - dstMinVals[i]);
1016 } else {
1017 dstScaleFactor[i] =
1018 ((float) ((1 << dstSM.getSampleSize(i)) - 1)) /
1019 (dstMaxVals[i] - dstMinVals[i]);
1020 }
1021 }
1022 }
1023 int ys = src.getMinY();
1024 int yd = dst.getMinY();
1025 int xs, xd;
1026 float sample;
1027 float[] color = new float[srcNumBands];
1028 float[] tmpColor;
1029 ColorSpace srcColorSpace = CSList[0];
1030 ColorSpace dstColorSpace = CSList[1];
1031 // process each pixel
1032 for (int y = 0; y < h; y++, ys++, yd++) {
1033 // get src scanline
1034 xs = src.getMinX();
1035 xd = dst.getMinX();
1036 for (int x = 0; x < w; x++, xs++, xd++) {
1037 for (int i = 0; i < srcNumBands; i++) {
1038 sample = src.getSampleFloat(xs, ys, i);
1039 if (!srcIsFloat) {
1040 sample = sample * srcScaleFactor[i] + srcMinVals[i];
1041 }
1042 color[i] = sample;
1043 }
1044 tmpColor = srcColorSpace.toCIEXYZ(color);
1045 tmpColor = dstColorSpace.fromCIEXYZ(tmpColor);
1046 for (int i = 0; i < dstNumBands; i++) {
1047 sample = tmpColor[i];
1048 if (!dstIsFloat) {
1049 sample = (sample - dstMinVals[i]) * dstScaleFactor[i];
1050 }
1051 dst.setSample(xd, yd, i, sample);
1052 }
1053 }
1054 }
1055 return dst;
1056 }
1057
1058 private void getMinMaxValsFromProfiles(ICC_Profile srcProfile,
1059 ICC_Profile dstProfile) {
1060 int type = srcProfile.getColorSpaceType();
1061 int nc = srcProfile.getNumComponents();
1062 srcMinVals = new float[nc];
1063 srcMaxVals = new float[nc];
1064 setMinMax(type, nc, srcMinVals, srcMaxVals);
1065 type = dstProfile.getColorSpaceType();
1066 nc = dstProfile.getNumComponents();
1067 dstMinVals = new float[nc];
1068 dstMaxVals = new float[nc];
1069 setMinMax(type, nc, dstMinVals, dstMaxVals);
1070 }
1071
1072 private void setMinMax(int type, int nc, float[] minVals, float[] maxVals) {
1073 if (type == ColorSpace.TYPE_Lab) {
1074 minVals[0] = 0.0f; // L
1075 maxVals[0] = 100.0f;
1076 minVals[1] = -128.0f; // a
1077 maxVals[1] = 127.0f;
1078 minVals[2] = -128.0f; // b
1079 maxVals[2] = 127.0f;
1080 } else if (type == ColorSpace.TYPE_XYZ) {
1081 minVals[0] = minVals[1] = minVals[2] = 0.0f; // X, Y, Z
1082 maxVals[0] = maxVals[1] = maxVals[2] = 1.0f + (32767.0f/ 32768.0f);
1083 } else {
1084 for (int i = 0; i < nc; i++) {
1085 minVals[i] = 0.0f;
1086 maxVals[i] = 1.0f;
1087 }
1088 }
1089 }
1090
1091 private void getMinMaxValsFromColorSpaces(ColorSpace srcCspace,
1092 ColorSpace dstCspace) {
1093 int nc = srcCspace.getNumComponents();
1094 srcMinVals = new float[nc];
1095 srcMaxVals = new float[nc];
1096 for (int i = 0; i < nc; i++) {
1097 srcMinVals[i] = srcCspace.getMinValue(i);
1098 srcMaxVals[i] = srcCspace.getMaxValue(i);
1099 }
1100 nc = dstCspace.getNumComponents();
1101 dstMinVals = new float[nc];
1102 dstMaxVals = new float[nc];
1103 for (int i = 0; i < nc; i++) {
1104 dstMinVals[i] = dstCspace.getMinValue(i);
1105 dstMaxVals[i] = dstCspace.getMaxValue(i);
1106 }
1107 }
1108
1109 }