Frames | No Frames |
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: }