Source for java.awt.image.ConvolveOp

   1: /* ConvolveOp.java --
   2:    Copyright (C) 2004, 2005, 2006, Free Software Foundation -- ConvolveOp
   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.geom.Point2D;
  44: import java.awt.geom.Rectangle2D;
  45: 
  46: /**
  47:  * Convolution filter.
  48:  * 
  49:  * ConvolveOp convolves the source image with a Kernel to generate a
  50:  * destination image.  This involves multiplying each pixel and its neighbors
  51:  * with elements in the kernel to compute a new pixel.
  52:  * 
  53:  * Each band in a Raster is convolved and copied to the destination Raster.
  54:  * 
  55:  * For BufferedImages, convolution is applied to all components.  If the
  56:  * source is not premultiplied, the data will be premultiplied before
  57:  * convolving.  Premultiplication will be undone if the destination is not
  58:  * premultiplied.  Color conversion will be applied if needed.
  59:  * 
  60:  * @author jlquinn@optonline.net
  61:  */
  62: public class ConvolveOp implements BufferedImageOp, RasterOp
  63: {
  64:   /** Edge pixels are set to 0. */
  65:   public static final int EDGE_ZERO_FILL = 0;
  66:   
  67:   /** Edge pixels are copied from the source. */
  68:   public static final int EDGE_NO_OP = 1;
  69:   
  70:   private Kernel kernel;
  71:   private int edge;
  72:   private RenderingHints hints;
  73: 
  74:   /**
  75:    * Construct a ConvolveOp.
  76:    * 
  77:    * The edge condition specifies that pixels outside the area that can be
  78:    * filtered are either set to 0 or copied from the source image.
  79:    * 
  80:    * @param kernel The kernel to convolve with.
  81:    * @param edgeCondition Either EDGE_ZERO_FILL or EDGE_NO_OP.
  82:    * @param hints Rendering hints for color conversion, or null.
  83:    */
  84:   public ConvolveOp(Kernel kernel,
  85:                       int edgeCondition,
  86:                       RenderingHints hints)
  87:   {
  88:     this.kernel = kernel;
  89:     edge = edgeCondition;
  90:     this.hints = hints;
  91:   }
  92:   
  93:   /**
  94:    * Construct a ConvolveOp.
  95:    * 
  96:    * The edge condition defaults to EDGE_ZERO_FILL.
  97:    * 
  98:    * @param kernel The kernel to convolve with.
  99:    */
 100:   public ConvolveOp(Kernel kernel)
 101:   {
 102:     this.kernel = kernel;
 103:     edge = EDGE_ZERO_FILL;
 104:     hints = null;
 105:   }
 106: 
 107:   
 108:   /* (non-Javadoc)
 109:    * @see java.awt.image.BufferedImageOp#filter(java.awt.image.BufferedImage,
 110:    * java.awt.image.BufferedImage)
 111:    */
 112:   public final BufferedImage filter(BufferedImage src, BufferedImage dst)
 113:   {
 114:     if (src == dst)
 115:       throw new IllegalArgumentException();
 116:     
 117:     if (dst == null)
 118:       dst = createCompatibleDestImage(src, src.getColorModel());
 119:     
 120:     // Make sure source image is premultiplied
 121:     BufferedImage src1 = src;
 122:     if (!src.isPremultiplied)
 123:     {
 124:       src1 = createCompatibleDestImage(src, src.getColorModel());
 125:       src.copyData(src1.getRaster());
 126:       src1.coerceData(true);
 127:     }
 128: 
 129:     BufferedImage dst1 = dst;
 130:     if (!src.getColorModel().equals(dst.getColorModel()))
 131:       dst1 = createCompatibleDestImage(src, src.getColorModel());
 132: 
 133:     filter(src1.getRaster(), dst1.getRaster());
 134:     
 135:     if (dst1 != dst)
 136:     {
 137:       // Convert between color models.
 138:       // TODO Check that premultiplied alpha is handled correctly here.
 139:       Graphics2D gg = dst.createGraphics();
 140:       gg.setRenderingHints(hints);
 141:       gg.drawImage(dst1, 0, 0, null);
 142:       gg.dispose();
 143:     }
 144:     
 145:     return dst;
 146:   }
 147: 
 148:   /* (non-Javadoc)
 149:    * @see
 150:    * java.awt.image.BufferedImageOp#createCompatibleDestImage(java.awt.image.BufferedImage,
 151:    * java.awt.image.ColorModel)
 152:    */
 153:   public BufferedImage createCompatibleDestImage(BufferedImage src,
 154:                          ColorModel dstCM)
 155:   {
 156:     // FIXME: set properties to those in src
 157:     return new BufferedImage(dstCM,
 158:                  src.getRaster().createCompatibleWritableRaster(),
 159:                  src.isPremultiplied, null);
 160:   }
 161: 
 162:   /* (non-Javadoc)
 163:    * @see java.awt.image.RasterOp#getRenderingHints()
 164:    */
 165:   public final RenderingHints getRenderingHints()
 166:   {
 167:     return hints;
 168:   }
 169:   
 170:   /**
 171:    * @return The edge condition.
 172:    */
 173:   public int getEdgeCondition()
 174:   {
 175:     return edge;
 176:   }
 177:   
 178:   /**
 179:    * Returns (a clone of) the convolution kernel.
 180:    *
 181:    * @return The convolution kernel.
 182:    */
 183:   public final Kernel getKernel()
 184:   {
 185:     return (Kernel) kernel.clone();
 186:   }
 187: 
 188:   /* (non-Javadoc)
 189:    * @see java.awt.image.RasterOp#filter(java.awt.image.Raster,
 190:    * java.awt.image.WritableRaster)
 191:    */
 192:   public final WritableRaster filter(Raster src, WritableRaster dest)
 193:   {
 194:     if (src == dest)
 195:       throw new IllegalArgumentException("src == dest is not allowed.");
 196:     if (kernel.getWidth() > src.getWidth() 
 197:         || kernel.getHeight() > src.getHeight())
 198:       throw new ImagingOpException("The kernel is too large.");
 199:     if (dest == null)
 200:       dest = createCompatibleDestRaster(src);
 201:     else if (src.getNumBands() != dest.getNumBands())
 202:       throw new ImagingOpException("src and dest have different band counts.");
 203: 
 204:     // calculate the borders that the op can't reach...
 205:     int kWidth = kernel.getWidth();
 206:     int kHeight = kernel.getHeight();
 207:     int left = kernel.getXOrigin();
 208:     int right = Math.max(kWidth - left - 1, 0);
 209:     int top = kernel.getYOrigin();
 210:     int bottom = Math.max(kHeight - top - 1, 0);
 211:     
 212:     // process the region that is reachable...
 213:     int regionW = src.width - left - right;
 214:     int regionH = src.height - top - bottom;
 215:     float[] kvals = kernel.getKernelData(null);
 216:     float[] tmp = new float[kWidth * kHeight];
 217: 
 218:     for (int x = 0; x < regionW; x++)
 219:       {
 220:         for (int y = 0; y < regionH; y++)
 221:           {
 222:             // FIXME: This needs a much more efficient implementation
 223:             for (int b = 0; b < src.getNumBands(); b++)
 224:             {
 225:               float v = 0;
 226:               src.getSamples(x, y, kWidth, kHeight, b, tmp);
 227:               for (int i = 0; i < tmp.length; i++)
 228:                 v += tmp[tmp.length - i - 1] * kvals[i];
 229:                 // FIXME: in the above line, I've had to reverse the order of 
 230:                 // the samples array to make the tests pass.  I haven't worked 
 231:                 // out why this is necessary. 
 232:               dest.setSample(x + kernel.getXOrigin(), y + kernel.getYOrigin(), 
 233:                              b, v);
 234:             }
 235:           }
 236:       }
 237:     
 238:     // fill in the top border
 239:     fillEdge(src, dest, 0, 0, src.width, top, edge);
 240:     
 241:     // fill in the bottom border
 242:     fillEdge(src, dest, 0, src.height - bottom, src.width, bottom, edge);
 243:     
 244:     // fill in the left border
 245:     fillEdge(src, dest, 0, top, left, regionH, edge);
 246:     
 247:     // fill in the right border
 248:     fillEdge(src, dest, src.width - right, top, right, regionH, edge);
 249:     
 250:     return dest;  
 251:   }
 252:   
 253:   /**
 254:    * Fills a range of pixels (typically at the edge of a raster) with either
 255:    * zero values (if <code>edgeOp</code> is <code>EDGE_ZERO_FILL</code>) or the 
 256:    * corresponding pixel values from the source raster (if <code>edgeOp</code>
 257:    * is <code>EDGE_NO_OP</code>).  This utility method is called by the 
 258:    * {@link #fillEdge(Raster, WritableRaster, int, int, int, int, int)} method.
 259:    * 
 260:    * @param src  the source raster.
 261:    * @param dest  the destination raster.
 262:    * @param x  the x-coordinate of the top left pixel in the range.
 263:    * @param y  the y-coordinate of the top left pixel in the range.
 264:    * @param w  the width of the pixel range.
 265:    * @param h  the height of the pixel range.
 266:    * @param edgeOp  indicates how to determine the values for the range
 267:    *     (either {@link #EDGE_ZERO_FILL} or {@link #EDGE_NO_OP}).
 268:    */
 269:   private void fillEdge(Raster src, WritableRaster dest, int x, int y, int w, 
 270:                         int h, int edgeOp) 
 271:   {
 272:     if (w <= 0)
 273:       return;
 274:     if (h <= 0)
 275:       return;
 276:     if (edgeOp == EDGE_ZERO_FILL)  // fill region with zeroes
 277:       {
 278:         float[] zeros = new float[src.getNumBands() * w * h];
 279:         dest.setPixels(x, y, w, h, zeros); 
 280:       }
 281:     else  // copy pixels from source
 282:       {
 283:         float[] pixels = new float[src.getNumBands() * w * h];
 284:         src.getPixels(x, y, w, h, pixels);
 285:         dest.setPixels(x, y, w, h, pixels);
 286:       }
 287:   }
 288: 
 289:   /* (non-Javadoc)
 290:    * @see java.awt.image.RasterOp#createCompatibleDestRaster(java.awt.image.Raster)
 291:    */
 292:   public WritableRaster createCompatibleDestRaster(Raster src)
 293:   {
 294:     return src.createCompatibleWritableRaster();
 295:   }
 296: 
 297:   /* (non-Javadoc)
 298:    * @see java.awt.image.BufferedImageOp#getBounds2D(java.awt.image.BufferedImage)
 299:    */
 300:   public final Rectangle2D getBounds2D(BufferedImage src)
 301:   {
 302:     return src.getRaster().getBounds();
 303:   }
 304: 
 305:   /* (non-Javadoc)
 306:    * @see java.awt.image.RasterOp#getBounds2D(java.awt.image.Raster)
 307:    */
 308:   public final Rectangle2D getBounds2D(Raster src)
 309:   {
 310:     return src.getBounds();
 311:   }
 312: 
 313:   /** Return corresponding destination point for source point.
 314:    * 
 315:    * ConvolveOp will return the value of src unchanged.
 316:    * @param src The source point.
 317:    * @param dst The destination point.
 318:    * @see java.awt.image.RasterOp#getPoint2D(java.awt.geom.Point2D,
 319:    * java.awt.geom.Point2D)
 320:    */
 321:   public final Point2D getPoint2D(Point2D src, Point2D dst)
 322:   {
 323:     if (dst == null) return (Point2D)src.clone();
 324:     dst.setLocation(src);
 325:     return dst;
 326:   }
 327: }