Source for java.awt.image.ConvolveOp

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