Source for java.awt.image.ColorConvertOp

   1: /* ColorConvertOp.java --
   2:    Copyright (C) 2004, 2006  Free Software Foundation
   3: 
   4: This file is part of GNU Classpath.
   5: 
   6: GNU Classpath is free software; you can redistribute it and/or modify
   7: it under the terms of the GNU General Public License as published by
   8: the Free Software Foundation; either version 2, or (at your option)
   9: any later version.
  10: 
  11: GNU Classpath is distributed in the hope that it will be useful, but
  12: WITHOUT ANY WARRANTY; without even the implied warranty of
  13: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  14: General Public License for more details.
  15: 
  16: You should have received a copy of the GNU General Public License
  17: along with GNU Classpath; see the file COPYING.  If not, write to the
  18: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  19: 02110-1301 USA.
  20: 
  21: Linking this library statically or dynamically with other modules is
  22: making a combined work based on this library.  Thus, the terms and
  23: conditions of the GNU General Public License cover the whole
  24: combination.
  25: 
  26: As a special exception, the copyright holders of this library give you
  27: permission to link this library with independent modules to produce an
  28: executable, regardless of the license terms of these independent
  29: modules, and to copy and distribute the resulting executable under
  30: terms of your choice, provided that you also meet, for each linked
  31: independent module, the terms and conditions of the license of that
  32: module.  An independent module is a module which is not derived from
  33: or based on this library.  If you modify this library, you may extend
  34: this exception to your version of the library, but you are not
  35: obligated to do so.  If you do not wish to do so, delete this
  36: exception statement from your version. */
  37: 
  38: 
  39: package java.awt.image;
  40: 
  41: import java.awt.Graphics2D;
  42: import java.awt.RenderingHints;
  43: import java.awt.color.ColorSpace;
  44: import java.awt.color.ICC_ColorSpace;
  45: import java.awt.color.ICC_Profile;
  46: import java.awt.geom.Point2D;
  47: import java.awt.geom.Rectangle2D;
  48: 
  49: /**
  50:  * ColorConvertOp is a filter for converting an image from one colorspace to
  51:  * another colorspace.  The filter can convert the image through a sequence
  52:  * of colorspaces or just from source to destination.
  53:  * 
  54:  * Color conversion is done on the color components without alpha.  Thus
  55:  * if a BufferedImage has alpha premultiplied, this is divided out before
  56:  * color conversion, and premultiplication applied if the destination
  57:  * requires it.
  58:  * 
  59:  * Color rendering and dithering hints may be applied if specified.  This is
  60:  * likely platform-dependent.
  61:  * 
  62:  * @author jlquinn@optonline.net
  63:  */
  64: public class ColorConvertOp implements BufferedImageOp, RasterOp
  65: {
  66:   private ColorSpace srccs;
  67:   private ColorSpace dstcs;
  68:   private RenderingHints hints;
  69:   private ICC_Profile[] profiles;
  70:   private ColorSpace[] spaces;
  71:   private boolean rasterValid;
  72:   
  73: 
  74:   /**
  75:    * Convert BufferedImage through a ColorSpace.
  76:    * 
  77:    * This filter version is only valid for BufferedImages.  The source image
  78:    * is converted to cspace.  If the destination is not null, it is then
  79:    * converted to the destination colorspace.  Normally this filter will only
  80:    * be used with a null destination.
  81:    * 
  82:    * @param cspace The target color space.
  83:    * @param hints Rendering hints to use in conversion, or null.
  84:    */
  85:   public ColorConvertOp(ColorSpace cspace, RenderingHints hints)
  86:   {
  87:     if (cspace == null)
  88:       throw new NullPointerException();
  89:     spaces = new ColorSpace[]{cspace};
  90:     this.hints = hints;
  91:     rasterValid = false;
  92:   }
  93:   
  94:   public ColorConvertOp(ColorSpace srcCspace, ColorSpace dstCspace,
  95:             RenderingHints hints)
  96:   {
  97:     if (srcCspace == null || dstCspace == null)
  98:       throw new NullPointerException();
  99:     spaces = new ColorSpace[]{srcCspace, dstCspace};
 100:     this.hints = hints;     
 101:   }
 102: 
 103:   /**
 104:    * Convert from a source image destination image color space.
 105:    * 
 106:    * This constructor builds a ColorConvertOp from an array of ICC_Profiles.
 107:    * The source image will be converted through the sequence of color spaces
 108:    * defined by the profiles.  If the sequence of profiles doesn't give a
 109:    * well-defined conversion, throws IllegalArgumentException.
 110:    * 
 111:    * NOTE: Sun's docs don't clearly define what a well-defined conversion is
 112:    *  - or perhaps someone smarter can come along and sort it out.  
 113:    * 
 114:    * For BufferedImages, when the first and last profiles match the
 115:    * requirements of the source and destination color space respectively, the
 116:    * corresponding conversion is unnecessary.  TODO: code this up.  I don't
 117:    * yet understand how you determine this.
 118:    * 
 119:    * For Rasters, the first and last profiles must have the same number of
 120:    * bands as the source and destination Rasters, respectively.  If this is
 121:    * not the case, or there fewer than 2 profiles, an IllegalArgumentException
 122:    * will be thrown. 
 123:    * 
 124:    * @param profiles
 125:    * @param hints
 126:    */
 127:   public ColorConvertOp(ICC_Profile[] profiles, RenderingHints hints)
 128:   {
 129:     if (profiles == null)
 130:       throw new NullPointerException();
 131:     this.hints = hints; 
 132:     this.profiles = profiles;
 133:     // TODO: Determine if this is well-defined.
 134:     // Create colorspace array with space for src and dest colorspace
 135:     spaces = new ColorSpace[profiles.length];
 136:     for (int i = 0; i < profiles.length; i++)
 137:       spaces[i] = new ICC_ColorSpace(profiles[i]);
 138:   }
 139:   
 140:   /** Convert from source image color space to destination image color space.
 141:    * 
 142:    * Only valid for BufferedImage objects, this Op converts from the source
 143:    * color space to the destination color space.  The destination can't be
 144:    * null for this operation.
 145:    * 
 146:    * @param hints Rendering hints to use during conversion, or null.
 147:    */
 148:   public ColorConvertOp(RenderingHints hints)
 149:   {
 150:     this.hints = hints; 
 151:     srccs = null;
 152:     dstcs = null;
 153:     rasterValid = false;
 154:   }
 155:   
 156:   /* (non-Javadoc)
 157:    * @see java.awt.image.BufferedImageOp#filter(java.awt.image.BufferedImage,
 158:    java.awt.image.BufferedImage)
 159:    */
 160:   public final BufferedImage filter(BufferedImage src, BufferedImage dst)
 161:   {
 162:     // TODO: The plan is to create a scanline buffer for intermediate buffers.
 163:     // For now we just suck it up and create intermediate buffers.
 164:     
 165:     if (dst == null && spaces.length == 0)
 166:       throw new IllegalArgumentException();
 167: 
 168:     // Make sure input isn't premultiplied by alpha
 169:     if (src.isAlphaPremultiplied())
 170:     {
 171:       BufferedImage tmp = createCompatibleDestImage(src, src.getColorModel());
 172:       copyimage(src, tmp);
 173:       tmp.coerceData(false);
 174:       src = tmp;
 175:     }
 176: 
 177:     ColorModel scm = src.getColorModel();
 178:     for (int i = 0; i < spaces.length; i++)
 179:     {
 180:       BufferedImage tmp = createCompatibleDestImage(src, scm);
 181:       copyimage(src, tmp);
 182:       src = tmp;
 183:     }
 184: 
 185:     // Intermediate conversions leave result in src
 186:     if (dst == null)
 187:       return src;
 188:     
 189:     // Apply final conversion
 190:     copyimage(src, dst);
 191:     
 192:     return dst;
 193:   }
 194: 
 195:   /* (non-Javadoc)
 196:    * @see java.awt.image.BufferedImageOp#createCompatibleDestImage(java.awt.image.BufferedImage, java.awt.image.ColorModel)
 197:    */
 198:   public BufferedImage createCompatibleDestImage(BufferedImage src,
 199:                          ColorModel dstCM)
 200:   {
 201:     // FIXME: set properties to those in src
 202:     return new BufferedImage(dstCM,
 203:                  src.getRaster().createCompatibleWritableRaster(),
 204:                  src.isPremultiplied,
 205:                  null);
 206:   }
 207:   
 208:   public final ICC_Profile[] getICC_Profiles()
 209:   {
 210:     return profiles;
 211:   }
 212: 
 213:   /** Return the rendering hints for this op. */
 214:   public final RenderingHints getRenderingHints()
 215:   {
 216:     return hints;
 217:   }
 218: 
 219:   /* (non-Javadoc)
 220:    * @see java.awt.image.RasterOp#filter(java.awt.image.Raster, java.awt.image.WritableRaster)
 221:    */
 222:   public final WritableRaster filter(Raster src, WritableRaster dest)
 223:   {
 224:     if (!rasterValid)
 225:       throw new IllegalArgumentException();
 226:     
 227:     // Need to iterate through each color space - there must be at least 2
 228:     for (int i = 1; i < spaces.length - 1; i++)
 229:     {
 230:       // FIXME: this is wrong.  tmp needs to have the same number of bands as
 231:       // spaces[i] has.
 232:       WritableRaster tmp = createCompatibleDestRaster(src);
 233:       copyraster(src, spaces[i - 1], tmp, spaces[i]);
 234:       src = tmp;
 235:     }
 236:     
 237:     // FIXME: this is wrong.  dst needs to have the same number of bands as
 238:     // spaces[i] has.
 239:     if (dest == null)
 240:       dest = createCompatibleDestRaster(src);
 241:     copyraster(src, spaces[spaces.length - 2],
 242:            dest, spaces[spaces.length - 1]);
 243:     
 244:     return dest;
 245:   }
 246: 
 247:   /* (non-Javadoc)
 248:    * @see java.awt.image.RasterOp#createCompatibleDestRaster(java.awt.image.Raster)
 249:    */
 250:   public WritableRaster createCompatibleDestRaster(Raster src)
 251:   {
 252:     return src.createCompatibleWritableRaster();
 253:   }
 254: 
 255:   /** Return corresponding destination point for source point.
 256:    * 
 257:    * LookupOp will return the value of src unchanged.
 258:    * @param src The source point.
 259:    * @param dst The destination point.
 260:    * @see java.awt.image.RasterOp#getPoint2D(java.awt.geom.Point2D, java.awt.geom.Point2D)
 261:    */
 262:   public final Point2D getPoint2D(Point2D src, Point2D dst)
 263:   {
 264:     if (dst == null) return (Point2D)src.clone();
 265:     dst.setLocation(src);
 266:     return dst;
 267:   }
 268: 
 269:   /* (non-Javadoc)
 270:    * @see java.awt.image.BufferedImageOp#getBounds2D(java.awt.image.BufferedImage)
 271:    */
 272:   public final Rectangle2D getBounds2D(BufferedImage src)
 273:   {
 274:     return src.getRaster().getBounds();
 275:   }
 276: 
 277:   /* (non-Javadoc)
 278:    * @see java.awt.image.RasterOp#getBounds2D(java.awt.image.Raster)
 279:    */
 280:   public final Rectangle2D getBounds2D(Raster src)
 281:   {
 282:     return src.getBounds();
 283:   }
 284:   
 285:   // According to Sven de Marothy, we need to copy the src into the dest
 286:   // using Graphics2D, in order to use the rendering hints.
 287:   private void copyimage(BufferedImage src, BufferedImage dst)
 288:   {
 289:     Graphics2D gg = dst.createGraphics();
 290:     
 291:     // If no hints are set there is no need to call
 292:     // setRenderingHints on the Graphics2D object.
 293:     if (hints != null)
 294:       gg.setRenderingHints(hints);
 295:     
 296:     gg.drawImage(src, 0, 0, null);
 297:     gg.dispose();
 298:   }
 299:   
 300:   private void copyraster(Raster src, ColorSpace scs, WritableRaster dst,
 301:                             ColorSpace dcs)
 302:   {
 303:     float[] sbuf = new float[src.getNumBands()];
 304:     
 305:     if (hints.get(RenderingHints.KEY_COLOR_RENDERING) ==
 306:         RenderingHints.VALUE_COLOR_RENDER_QUALITY)
 307:     {
 308:       // use cie for accuracy
 309:       for (int y = src.getMinY(); y < src.getHeight() + src.getMinY(); y++)
 310:         for (int x = src.getMinX(); x < src.getWidth() + src.getMinX(); x++)
 311:           dst.setPixel(x, y,
 312:                dcs.fromCIEXYZ(scs.toCIEXYZ(src.getPixel(x, y, sbuf))));
 313:     }
 314:     else
 315:     {
 316:       // use rgb - it's probably faster
 317:       for (int y = src.getMinY(); y < src.getHeight() + src.getMinY(); y++)
 318:         for (int x = src.getMinX(); x < src.getWidth() + src.getMinX(); x++)
 319:           dst.setPixel(x, y,
 320:                dcs.fromRGB(scs.toRGB(src.getPixel(x, y, sbuf))));
 321:     }
 322:   }
 323: 
 324: }