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 }