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 */ ConvolveOp(Kernel kernel, int edgeCondition, RenderingHints hints)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 */ ConvolveOp(Kernel kernel)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 */ filter(BufferedImage src, BufferedImage dst)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 */ createCompatibleDestImage(BufferedImage src, ColorModel dstCM)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 */ getRenderingHints()166 public RenderingHints getRenderingHints() 167 { 168 return hints; 169 } 170 171 /** 172 * @return The edge condition. 173 */ getEdgeCondition()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 */ getKernel()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 */ filter(Raster src, WritableRaster dest)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 */ createCompatibleDestRaster(Raster src)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 */ getBounds2D(BufferedImage src)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 */ getBounds2D(Raster src)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 */ getPoint2D(Point2D src, Point2D dst)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 } 340