Source for java.awt.image.LookupOp

   1: /* LookupOp.java -- Filter that converts each pixel using a lookup table.
   2:    Copyright (C) 2004  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.RenderingHints;
  42: import java.awt.geom.Point2D;
  43: import java.awt.geom.Rectangle2D;
  44: 
  45: /**
  46:  * LookupOp is a filter that converts each pixel using a lookup table.
  47:  *
  48:  * For filtering Rasters, the lookup table must have either one component
  49:  * that is applied to all bands, or one component for every band in the
  50:  * Rasters.
  51:  *
  52:  * For BufferedImages, the lookup table may apply to both color and alpha
  53:  * components.  If the lookup table contains one component, or if there are
  54:  * the same number of components as color components in the source, the table
  55:  * applies to all color components.  Otherwise the table applies to all
  56:  * components including alpha.  Alpha premultiplication is ignored during the
  57:  * lookup filtering.
  58:  *
  59:  * After filtering, if color conversion is necessary, the conversion happens,
  60:  * taking alpha premultiplication into account.
  61:  *
  62:  * @author jlquinn
  63:  */
  64: public class LookupOp implements BufferedImageOp, RasterOp
  65: {
  66:   private LookupTable lut;
  67:   private RenderingHints hints;
  68: 
  69:   /**
  70:    * Construct a new LookupOp using the given LookupTable.
  71:    *
  72:    * @param lookup LookupTable to use.
  73:    * @param hints Rendering hints (can be null).
  74:    */
  75:   public LookupOp(LookupTable lookup, RenderingHints hints)
  76:   {
  77:     lut = lookup;
  78:     this.hints = hints;
  79:   }
  80: 
  81:   /**
  82:    * Converts the source image using the lookup table specified in the
  83:    * constructor.  The resulting image is stored in the destination image if one
  84:    * is provided; otherwise a new BufferedImage is created and returned.
  85:    *
  86:    * The source image cannot use an IndexColorModel, and the destination image
  87:    * (if one is provided) must have the same size.
  88:    *
  89:    * @param src The source image.
  90:    * @param dst The destination image.
  91:    * @throws IllegalArgumentException if the rasters and/or color spaces are
  92:    *            incompatible.
  93:    * @throws ArrayIndexOutOfBoundsException if a pixel in the source is not
  94:    *    contained in the LookupTable.
  95:    * @return The convolved image.
  96:    */
  97:   public final BufferedImage filter(BufferedImage src, BufferedImage dst)
  98:   {
  99:     if (src.getColorModel() instanceof IndexColorModel)
 100:       throw new IllegalArgumentException("LookupOp.filter: IndexColorModel "
 101:                                          + "not allowed");
 102: 
 103:     if (lut.getNumComponents() != 1
 104:          && lut.getNumComponents() != src.getColorModel().getNumComponents()
 105:          && lut.getNumComponents() != src.getColorModel().getNumColorComponents())
 106:      throw new IllegalArgumentException("LookupOp.filter: Incompatible " +
 107:                 "lookup table and source image");
 108: 
 109:     if (dst == null)
 110:       dst = createCompatibleDestImage(src, null);
 111: 
 112:     else if (src.getHeight() != dst.getHeight() || src.getWidth() != dst.getWidth())
 113:       throw new IllegalArgumentException("Source and destination images are " +
 114:                 "different sizes.");
 115: 
 116:     // Set up for potential colormodel mismatch
 117:     BufferedImage tgt;
 118:     if (dst.getColorModel().equals(src.getColorModel()))
 119:       tgt = dst;
 120:     else
 121:       tgt = createCompatibleDestImage(src, src.getColorModel());
 122: 
 123:     Raster sr = src.getRaster();
 124:     WritableRaster dr = tgt.getRaster();
 125: 
 126:     if (src.getColorModel().hasAlpha() &&
 127:         (lut.getNumComponents() == 1 ||
 128:          lut.getNumComponents() == src.getColorModel().getNumColorComponents()))
 129:     {
 130:       // Need to ignore alpha for lookup
 131:       int[] dbuf = new int[src.getColorModel().getNumComponents()];
 132:       int tmpBands = src.getColorModel().getNumColorComponents();
 133:       int[] tmp = new int[tmpBands];
 134: 
 135:       // Filter the pixels
 136:       for (int y = src.getMinY(); y < src.getHeight() + src.getMinY(); y++)
 137:         for (int x = src.getMinX(); x < src.getWidth() + src.getMinX(); x++)
 138:         {
 139:           // Filter only color components, but also copy alpha
 140:           sr.getPixel(x, y, dbuf);
 141:           System.arraycopy(dbuf, 0, tmp, 0, tmpBands);
 142:           dr.setPixel(x, y, lut.lookupPixel(tmp, dbuf));
 143: 
 144:           /* The reference implementation does not use LookupTable.lookupPixel,
 145:            * but rather it seems to copy the table into a native array.  The
 146:            * effect of this (a probable bug in their implementation) is that
 147:            * an out-of-bounds lookup on a ByteLookupTable will *not* throw an
 148:            * out of bounds exception, but will instead return random garbage.
 149:            * A bad lookup on a ShortLookupTable, however, will throw an
 150:            * exception.
 151:            *
 152:            * Instead of mimicing this behaviour, we always throw an
 153:            * ArrayOutofBoundsException by virtue of using
 154:            * LookupTable.lookupPixle.
 155:            */
 156:         }
 157:     }
 158:     else
 159:     {
 160:       // No alpha to ignore
 161:       int[] dbuf = new int[src.getColorModel().getNumComponents()];
 162: 
 163:       // Filter the pixels
 164:       for (int y = src.getMinY(); y < src.getHeight() + src.getMinY(); y++)
 165:         for (int x = src.getMinX(); x < src.getWidth() + src.getMinX(); x++)
 166:           dr.setPixel(x, y, lut.lookupPixel(sr.getPixel(x, y, dbuf), dbuf));
 167:     }
 168: 
 169:     if (tgt != dst)
 170:       new ColorConvertOp(hints).filter(tgt, dst);
 171: 
 172:     return dst;
 173:   }
 174: 
 175:   /* (non-Javadoc)
 176:    * @see java.awt.image.BufferedImageOp#getBounds2D(java.awt.image.BufferedImage)
 177:    */
 178:   public final Rectangle2D getBounds2D(BufferedImage src)
 179:   {
 180:     return src.getRaster().getBounds();
 181:   }
 182: 
 183:   /* (non-Javadoc)
 184:    * @see java.awt.image.BufferedImageOp#createCompatibleDestImage(java.awt.image.BufferedImage, java.awt.image.ColorModel)
 185:    */
 186:   public BufferedImage createCompatibleDestImage(BufferedImage src,
 187:                                                  ColorModel dstCM)
 188:   {
 189:     if (dstCM != null)
 190:       return new BufferedImage(dstCM,
 191:                                src.getRaster().createCompatibleWritableRaster(),
 192:                                src.isAlphaPremultiplied(), null);
 193: 
 194:     // This is a strange exception, done for compatibility with the reference
 195:     // (as demonstrated by a mauve testcase)
 196:     int imgType = src.getType();
 197:     if (imgType == BufferedImage.TYPE_USHORT_GRAY)
 198:       imgType = BufferedImage.TYPE_BYTE_GRAY;
 199: 
 200:     return new BufferedImage(src.getWidth(), src.getHeight(), imgType);
 201:   }
 202: 
 203:   /**
 204:    * Returns the corresponding destination point for a given source point.
 205:    *
 206:    * This Op will return the source point unchanged.
 207:    *
 208:    * @param src The source point.
 209:    * @param dst The destination point.
 210:    */
 211:   public final Point2D getPoint2D(Point2D src, Point2D dst)
 212:   {
 213:     if (dst == null)
 214:       return (Point2D) src.clone();
 215: 
 216:     dst.setLocation(src);
 217:     return dst;
 218:   }
 219: 
 220:   /**
 221:    * Return the LookupTable for this op.
 222:    *
 223:    *  @return The lookup table.
 224:    */
 225:   public final LookupTable getTable()
 226:   {
 227:     return lut;
 228:   }
 229: 
 230:   /* (non-Javadoc)
 231:    * @see java.awt.image.RasterOp#getRenderingHints()
 232:    */
 233:   public final RenderingHints getRenderingHints()
 234:   {
 235:     return hints;
 236:   }
 237: 
 238:   /**
 239:    * Filter a raster through a lookup table.
 240:    *
 241:    * Applies the lookup table for this Rasterop to each pixel of src and
 242:    * puts the results in dest.  If dest is null, a new Raster is created and
 243:    * returned.
 244:    *
 245:    * @param src The source raster.
 246:    * @param dest The destination raster.
 247:    * @return The WritableRaster with the filtered pixels.
 248:    * @throws IllegalArgumentException if lookup table has more than one
 249:    *    component but not the same as src and dest.
 250:    * @throws ArrayIndexOutOfBoundsException if a pixel in the source is not
 251:    *    contained in the LookupTable.
 252:    */
 253:   public final WritableRaster filter(Raster src, WritableRaster dest)
 254:   {
 255:     if (dest == null)
 256:       // Allocate a raster if needed
 257:       dest = createCompatibleDestRaster(src);
 258:     else
 259:       if (src.getNumBands() != dest.getNumBands())
 260:         throw new IllegalArgumentException("Source and destination rasters " +
 261:                 "are incompatible.");
 262: 
 263:     if (lut.getNumComponents() != 1
 264:         && lut.getNumComponents() != src.getNumBands())
 265:       throw new IllegalArgumentException("Lookup table is incompatible with " +
 266:             "this raster.");
 267: 
 268:     // Allocate pixel storage.
 269:     int[] tmp = new int[src.getNumBands()];
 270: 
 271:     // Filter the pixels
 272:     for (int y = src.getMinY(); y < src.getHeight() + src.getMinY(); y++)
 273:       for (int x = src.getMinX(); x < src.getWidth() + src.getMinX(); x++)
 274:         dest.setPixel(x, y, lut.lookupPixel(src.getPixel(x, y, tmp), tmp));
 275: 
 276:     /* The reference implementation does not use LookupTable.lookupPixel,
 277:      * but rather it seems to copy the table into a native array.  The
 278:      * effect of this (a probable bug in their implementation) is that
 279:      * an out-of-bounds lookup on a ByteLookupTable will *not* throw an
 280:      * out of bounds exception, but will instead return random garbage.
 281:      * A bad lookup on a ShortLookupTable, however, will throw an
 282:      * exception.
 283:      *
 284:      * Instead of mimicing this behaviour, we always throw an
 285:      * ArrayOutofBoundsException by virtue of using
 286:      * LookupTable.lookupPixle.
 287:      */
 288:     return dest;
 289:   }
 290: 
 291:   /* (non-Javadoc)
 292:    * @see java.awt.image.RasterOp#getBounds2D(java.awt.image.Raster)
 293:    */
 294:   public final Rectangle2D getBounds2D(Raster src)
 295:   {
 296:     return src.getBounds();
 297:   }
 298: 
 299:   /* (non-Javadoc)
 300:    * @see java.awt.image.RasterOp#createCompatibleDestRaster(java.awt.image.Raster)
 301:    */
 302:   public WritableRaster createCompatibleDestRaster(Raster src)
 303:   {
 304:     return src.createCompatibleWritableRaster();
 305:   }
 306: 
 307: }