001 /* LookupOp.java -- Filter that converts each pixel using a lookup table. 002 Copyright (C) 2004 Free Software Foundation 003 004 This file is part of GNU Classpath. 005 006 GNU Classpath is free software; you can redistribute it and/or modify 007 it under the terms of the GNU General Public License as published by 008 the Free Software Foundation; either version 2, or (at your option) 009 any later version. 010 011 GNU Classpath is distributed in the hope that it will be useful, but 012 WITHOUT ANY WARRANTY; without even the implied warranty of 013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014 General Public License for more details. 015 016 You should have received a copy of the GNU General Public License 017 along with GNU Classpath; see the file COPYING. If not, write to the 018 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 019 02110-1301 USA. 020 021 Linking this library statically or dynamically with other modules is 022 making a combined work based on this library. Thus, the terms and 023 conditions of the GNU General Public License cover the whole 024 combination. 025 026 As a special exception, the copyright holders of this library give you 027 permission to link this library with independent modules to produce an 028 executable, regardless of the license terms of these independent 029 modules, and to copy and distribute the resulting executable under 030 terms of your choice, provided that you also meet, for each linked 031 independent module, the terms and conditions of the license of that 032 module. An independent module is a module which is not derived from 033 or based on this library. If you modify this library, you may extend 034 this exception to your version of the library, but you are not 035 obligated to do so. If you do not wish to do so, delete this 036 exception statement from your version. */ 037 038 039 package java.awt.image; 040 041 import java.awt.RenderingHints; 042 import java.awt.geom.Point2D; 043 import java.awt.geom.Rectangle2D; 044 045 /** 046 * LookupOp is a filter that converts each pixel using a lookup table. 047 * 048 * For filtering Rasters, the lookup table must have either one component 049 * that is applied to all bands, or one component for every band in the 050 * Rasters. 051 * 052 * For BufferedImages, the lookup table may apply to both color and alpha 053 * components. If the lookup table contains one component, or if there are 054 * the same number of components as color components in the source, the table 055 * applies to all color components. Otherwise the table applies to all 056 * components including alpha. Alpha premultiplication is ignored during the 057 * lookup filtering. 058 * 059 * After filtering, if color conversion is necessary, the conversion happens, 060 * taking alpha premultiplication into account. 061 * 062 * @author jlquinn 063 */ 064 public class LookupOp implements BufferedImageOp, RasterOp 065 { 066 private LookupTable lut; 067 private RenderingHints hints; 068 069 /** 070 * Construct a new LookupOp using the given LookupTable. 071 * 072 * @param lookup LookupTable to use. 073 * @param hints Rendering hints (can be null). 074 */ 075 public LookupOp(LookupTable lookup, RenderingHints hints) 076 { 077 lut = lookup; 078 this.hints = hints; 079 } 080 081 /** 082 * Converts the source image using the lookup table specified in the 083 * constructor. The resulting image is stored in the destination image if one 084 * is provided; otherwise a new BufferedImage is created and returned. 085 * 086 * The source image cannot use an IndexColorModel, and the destination image 087 * (if one is provided) must have the same size. 088 * 089 * @param src The source image. 090 * @param dst The destination image. 091 * @throws IllegalArgumentException if the rasters and/or color spaces are 092 * incompatible. 093 * @throws ArrayIndexOutOfBoundsException if a pixel in the source is not 094 * contained in the LookupTable. 095 * @return The convolved image. 096 */ 097 public final BufferedImage filter(BufferedImage src, BufferedImage dst) 098 { 099 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 }