Frames | No Frames |
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: }