Source for java.awt.image.ConvolveOp

   1: /* ConvolveOp.java --
   2:    Copyright (C) 2004 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:    * @return The convolution kernel.
 181:    */
 182:   public Kernel getKernel()
 183:   {
 184:     return kernel;
 185:   }
 186: 
 187:   /* (non-Javadoc)
 188:    * @see java.awt.image.RasterOp#filter(java.awt.image.Raster,
 189:    * java.awt.image.WritableRaster)
 190:    */
 191:   public WritableRaster filter(Raster src, WritableRaster dest) {
 192:     if (src.numBands != dest.numBands)
 193:       throw new ImagingOpException(null);
 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: 
 203:     // Deal with bottom edge
 204:     if (edge == EDGE_ZERO_FILL)
 205:     {
 206:       float[] zeros = new float[src.getNumBands() * src.getWidth()
 207:                 * (kernel.getYOrigin() - 1)];
 208:       Arrays.fill(zeros, 0);
 209:       dest.setPixels(src.getMinX(), src.getMinY(), src.getWidth(),
 210:              kernel.getYOrigin() - 1, zeros);
 211:     }
 212:     else
 213:     {
 214:       float[] vals = new float[src.getNumBands() * src.getWidth()
 215:                    * (kernel.getYOrigin() - 1)];
 216:       src.getPixels(src.getMinX(), src.getMinY(), src.getWidth(),
 217:             kernel.getYOrigin() - 1, vals);
 218:       dest.setPixels(src.getMinX(), src.getMinY(), src.getWidth(),
 219:              kernel.getYOrigin() - 1, vals);
 220:     }
 221:     
 222:     // Handle main section
 223:     float[] kvals = kernel.getKernelData(null);
 224: 
 225:     float[] tmp = new float[kernel.getWidth() * kernel.getHeight()];
 226:     for (int y = src.getMinY() + kernel.getYOrigin();
 227:          y < src.getMinY() + src.getHeight() - kernel.getYOrigin() / 2; y++)
 228:     {
 229:       // Handle unfiltered edge pixels at start of line
 230:       float[] t1 = new float[(kernel.getXOrigin() - 1) * src.getNumBands()];
 231:       if (edge == EDGE_ZERO_FILL)
 232:         Arrays.fill(t1, 0);
 233:       else
 234:         src.getPixels(src.getMinX(), y, kernel.getXOrigin() - 1, 1, t1);
 235:       dest.setPixels(src.getMinX(), y, kernel.getXOrigin() - 1, 1, t1);
 236:       
 237:       for (int x = src.getMinX(); x < src.getWidth() + src.getMinX(); x++)
 238:       {
 239:         // FIXME: This needs a much more efficient implementation
 240:         for (int b = 0; b < src.getNumBands(); b++)
 241:         {
 242:           float v = 0;
 243:           src.getSamples(x, y, kernel.getWidth(), kernel.getHeight(), b, tmp);
 244:           for (int i=0; i < tmp.length; i++)
 245:             v += tmp[i] * kvals[i];
 246:           dest.setSample(x, y, b, v);
 247:         }
 248:       }
 249: 
 250:       // Handle unfiltered edge pixels at end of line
 251:       float[] t2 = new float[(kernel.getWidth() / 2) * src.getNumBands()];
 252:       if (edge == EDGE_ZERO_FILL)
 253:         Arrays.fill(t2, 0);
 254:       else
 255:         src.getPixels(src.getMinX() + src.getWidth()
 256:               - (kernel.getWidth() / 2),
 257:               y, kernel.getWidth() / 2, 1, t2);
 258:       dest.setPixels(src.getMinX() + src.getWidth() - (kernel.getWidth() / 2),
 259:              y, kernel.getWidth() / 2, 1, t2);
 260:     }
 261:     for (int y = src.getMinY(); y < src.getHeight() + src.getMinY(); y++)
 262:       for (int x = src.getMinX(); x< src.getWidth() + src.getMinX(); x++)
 263:       {
 264:         
 265:       }
 266:     for (int y = src.getMinY(); y < src.getHeight() + src.getMinY(); y++)
 267:       for (int x = src.getMinX(); x< src.getWidth() + src.getMinX(); x++)
 268:       {
 269:         
 270:       }
 271:       
 272:     // Handle top edge
 273:     if (edge == EDGE_ZERO_FILL)
 274:     {
 275:       float[] zeros = new float[src.getNumBands() * src.getWidth() *
 276:                                 (kernel.getHeight() / 2)];
 277:       Arrays.fill(zeros, 0);
 278:       dest.setPixels(src.getMinX(),
 279:           src.getHeight() + src.getMinY() - (kernel.getHeight() / 2),
 280:           src.getWidth(), kernel.getHeight() / 2, zeros);
 281:     }
 282:     else
 283:     {
 284:       float[] vals = new float[src.getNumBands() * src.getWidth() *
 285:                                (kernel.getHeight() / 2)];
 286:       src.getPixels(src.getMinX(),
 287:             src.getHeight() + src.getMinY()
 288:             - (kernel.getHeight() / 2),
 289:             src.getWidth(), kernel.getHeight() / 2, vals);
 290:       dest.setPixels(src.getMinX(),
 291:              src.getHeight() + src.getMinY()
 292:              - (kernel.getHeight() / 2),
 293:              src.getWidth(), kernel.getHeight() / 2, vals);
 294:     }
 295:     
 296:     return dest;
 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:   /* (non-Javadoc)
 308:    * @see java.awt.image.BufferedImageOp#getBounds2D(java.awt.image.BufferedImage)
 309:    */
 310:   public Rectangle2D getBounds2D(BufferedImage src)
 311:   {
 312:     return src.getRaster().getBounds();
 313:   }
 314: 
 315:   /* (non-Javadoc)
 316:    * @see java.awt.image.RasterOp#getBounds2D(java.awt.image.Raster)
 317:    */
 318:   public Rectangle2D getBounds2D(Raster src)
 319:   {
 320:     return src.getBounds();
 321:   }
 322: 
 323:   /** Return corresponding destination point for source point.
 324:    * 
 325:    * ConvolveOp will return the value of src unchanged.
 326:    * @param src The source point.
 327:    * @param dst The destination point.
 328:    * @see java.awt.image.RasterOp#getPoint2D(java.awt.geom.Point2D,
 329:    * java.awt.geom.Point2D)
 330:    */
 331:   public Point2D getPoint2D(Point2D src, Point2D dst)
 332:   {
 333:     if (dst == null) return (Point2D)src.clone();
 334:     dst.setLocation(src);
 335:     return dst;
 336:   }
 337: }