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 37 package java.awt.image; 38 39 import java.awt.Point; 40 import java.awt.Graphics2D; 41 import java.awt.color.*; 42 import sun.java2d.cmm.ColorTransform; 43 import sun.java2d.cmm.CMSManager; 44 import sun.java2d.cmm.ProfileDeferralMgr; 45 import sun.java2d.cmm.PCMM; 46 import java.awt.geom.Rectangle2D; 47 import java.awt.geom.Point2D; 48 import 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 */ 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} 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 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 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 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 }