2 Copyright (C) 2004, 2005 Free Software Foundation -- ConvolveOp
4 This file is part of GNU Classpath.
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)
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.
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
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
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. */
39 package java.awt.image;
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;
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.
54 * Each band in a Raster is convolved and copied to the destination Raster.
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.
61 * @author jlquinn@optonline.net
63 public class ConvolveOp implements BufferedImageOp, RasterOp
65 /** Edge pixels are set to 0. */
66 public static final int EDGE_ZERO_FILL = 0;
68 /** Edge pixels are copied from the source. */
69 public static final int EDGE_NO_OP = 1;
71 private Kernel kernel;
73 private RenderingHints hints;
76 * Construct a ConvolveOp.
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.
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.
85 public ConvolveOp(Kernel kernel,
95 * Construct a ConvolveOp.
97 * The edge condition defaults to EDGE_ZERO_FILL.
99 * @param kernel The kernel to convolve with.
101 public ConvolveOp(Kernel kernel)
103 this.kernel = kernel;
104 edge = EDGE_ZERO_FILL;
110 * @see java.awt.image.BufferedImageOp#filter(java.awt.image.BufferedImage,
111 * java.awt.image.BufferedImage)
113 public final BufferedImage filter(BufferedImage src, BufferedImage dst)
116 throw new IllegalArgumentException();
119 dst = createCompatibleDestImage(src, src.getColorModel());
121 // Make sure source image is premultiplied
122 BufferedImage src1 = src;
123 if (!src.isPremultiplied)
125 src1 = createCompatibleDestImage(src, src.getColorModel());
126 src.copyData(src1.getRaster());
127 src1.coerceData(true);
130 BufferedImage dst1 = dst;
131 if (!src.getColorModel().equals(dst.getColorModel()))
132 dst1 = createCompatibleDestImage(src, src.getColorModel());
134 filter(src1.getRaster(), dst1.getRaster());
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);
151 * java.awt.image.BufferedImageOp#createCompatibleDestImage(java.awt.image.BufferedImage,
152 * java.awt.image.ColorModel)
154 public BufferedImage createCompatibleDestImage(BufferedImage src,
157 // FIXME: set properties to those in src
158 return new BufferedImage(dstCM,
159 src.getRaster().createCompatibleWritableRaster(),
160 src.isPremultiplied, null);
164 * @see java.awt.image.RasterOp#getRenderingHints()
166 public final RenderingHints getRenderingHints()
172 * @return The edge condition.
174 public int getEdgeCondition()
180 * Returns (a clone of) the convolution kernel.
182 * @return The convolution kernel.
184 public final Kernel getKernel()
186 return (Kernel) kernel.clone();
190 * @see java.awt.image.RasterOp#filter(java.awt.image.Raster,
191 * java.awt.image.WritableRaster)
193 public final WritableRaster filter(Raster src, WritableRaster dest) {
195 throw new IllegalArgumentException();
196 if (src.getWidth() < kernel.getWidth() ||
197 src.getHeight() < kernel.getHeight())
198 throw new ImagingOpException(null);
201 dest = createCompatibleDestRaster(src);
202 else if (src.numBands != dest.numBands)
203 throw new ImagingOpException(null);
205 // Deal with bottom edge
206 if (edge == EDGE_ZERO_FILL)
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);
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);
224 // Handle main section
225 float[] kvals = kernel.getKernelData(null);
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++)
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)
236 src.getPixels(src.getMinX(), y, kernel.getXOrigin() - 1, 1, t1);
237 dest.setPixels(src.getMinX(), y, kernel.getXOrigin() - 1, 1, t1);
239 for (int x = src.getMinX(); x < src.getWidth() + src.getMinX(); x++)
241 // FIXME: This needs a much more efficient implementation
242 for (int b = 0; b < src.getNumBands(); b++)
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);
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)
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);
263 for (int y = src.getMinY(); y < src.getHeight() + src.getMinY(); y++)
264 for (int x = src.getMinX(); x< src.getWidth() + src.getMinX(); x++)
268 for (int y = src.getMinY(); y < src.getHeight() + src.getMinY(); y++)
269 for (int x = src.getMinX(); x< src.getWidth() + src.getMinX(); x++)
275 if (edge == EDGE_ZERO_FILL)
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);
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);
302 * @see java.awt.image.RasterOp#createCompatibleDestRaster(java.awt.image.Raster)
304 public WritableRaster createCompatibleDestRaster(Raster src)
306 return src.createCompatibleWritableRaster();
310 * @see java.awt.image.BufferedImageOp#getBounds2D(java.awt.image.BufferedImage)
312 public final Rectangle2D getBounds2D(BufferedImage src)
314 return src.getRaster().getBounds();
318 * @see java.awt.image.RasterOp#getBounds2D(java.awt.image.Raster)
320 public final Rectangle2D getBounds2D(Raster src)
322 return src.getBounds();
325 /** Return corresponding destination point for source point.
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)
333 public final Point2D getPoint2D(Point2D src, Point2D dst)
335 if (dst == null) return (Point2D)src.clone();
336 dst.setLocation(src);