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    }