Home » openjdk-7 » java » awt » image » [javadoc | source]

    1   /*
    2    * Copyright (c) 1997, 2008, 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    *** 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   }

Home » openjdk-7 » java » awt » image » [javadoc | source]