001/* 002Copyright 2006 Jerry Huxtable 003 004Licensed under the Apache License, Version 2.0 (the "License"); 005you may not use this file except in compliance with the License. 006You may obtain a copy of the License at 007 008 http://www.apache.org/licenses/LICENSE-2.0 009 010Unless required by applicable law or agreed to in writing, software 011distributed under the License is distributed on an "AS IS" BASIS, 012WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013See the License for the specific language governing permissions and 014limitations under the License. 015*/ 016 017package com.jhlabs.image; 018 019import java.awt.*; 020import java.awt.image.*; 021import java.awt.geom.*; 022 023/** 024 * A filter which applies a convolution kernel to an image. 025 * @author Jerry Huxtable 026 */ 027public class ConvolveFilter extends AbstractBufferedImageOp { 028 029 /** 030 * Treat pixels off the edge as zero. 031 */ 032 public static int ZERO_EDGES = 0; 033 034 /** 035 * Clamp pixels off the edge to the nearest edge. 036 */ 037 public static int CLAMP_EDGES = 1; 038 039 /** 040 * Wrap pixels off the edge to the opposite edge. 041 */ 042 public static int WRAP_EDGES = 2; 043 044 /** 045 * The convolution kernel. 046 */ 047 protected Kernel kernel = null; 048 049 /** 050 * Whether to convolve alpha. 051 */ 052 protected boolean alpha = true; 053 054 /** 055 * Whether to promultiply the alpha before convolving. 056 */ 057 protected boolean premultiplyAlpha = true; 058 059 /** 060 * What do do at the image edges. 061 */ 062 private int edgeAction = CLAMP_EDGES; 063 064 /** 065 * Construct a filter with a null kernel. This is only useful if you're going to change the kernel later on. 066 */ 067 public ConvolveFilter() { 068 this(new float[9]); 069 } 070 071 /** 072 * Construct a filter with the given 3x3 kernel. 073 * @param matrix an array of 9 floats containing the kernel 074 */ 075 public ConvolveFilter(float[] matrix) { 076 this(new Kernel(3, 3, matrix)); 077 } 078 079 /** 080 * Construct a filter with the given kernel. 081 * @param rows the number of rows in the kernel 082 * @param cols the number of columns in the kernel 083 * @param matrix an array of rows*cols floats containing the kernel 084 */ 085 public ConvolveFilter(int rows, int cols, float[] matrix) { 086 this(new Kernel(cols, rows, matrix)); 087 } 088 089 /** 090 * Construct a filter with the given 3x3 kernel. 091 * @param kernel the convolution kernel 092 */ 093 public ConvolveFilter(Kernel kernel) { 094 this.kernel = kernel; 095 } 096 097 /** 098 * Set the convolution kernel. 099 * @param kernel the kernel 100 * @see #getKernel 101 */ 102 public void setKernel(Kernel kernel) { 103 this.kernel = kernel; 104 } 105 106 /** 107 * Get the convolution kernel. 108 * @return the kernel 109 * @see #setKernel 110 */ 111 public Kernel getKernel() { 112 return kernel; 113 } 114 115 /** 116 * Set the action to perfomr for pixels off the image edges. 117 * @param edgeAction the action 118 * @see #getEdgeAction 119 */ 120 public void setEdgeAction(int edgeAction) { 121 this.edgeAction = edgeAction; 122 } 123 124 /** 125 * Get the action to perfomr for pixels off the image edges. 126 * @return the action 127 * @see #setEdgeAction 128 */ 129 public int getEdgeAction() { 130 return edgeAction; 131 } 132 133 /** 134 * Set whether to convolve the alpha channel. 135 * @param useAlpha true to convolve the alpha 136 * @see #getUseAlpha 137 */ 138 public void setUseAlpha( boolean useAlpha ) { 139 this.alpha = useAlpha; 140 } 141 142 /** 143 * Get whether to convolve the alpha channel. 144 * @return true to convolve the alpha 145 * @see #setUseAlpha 146 */ 147 public boolean getUseAlpha() { 148 return alpha; 149 } 150 151 /** 152 * Set whether to premultiply the alpha channel. 153 * @param premultiplyAlpha true to premultiply the alpha 154 * @see #getPremultiplyAlpha 155 */ 156 public void setPremultiplyAlpha( boolean premultiplyAlpha ) { 157 this.premultiplyAlpha = premultiplyAlpha; 158 } 159 160 /** 161 * Get whether to premultiply the alpha channel. 162 * @return true to premultiply the alpha 163 * @see #setPremultiplyAlpha 164 */ 165 public boolean getPremultiplyAlpha() { 166 return premultiplyAlpha; 167 } 168 169 public BufferedImage filter( BufferedImage src, BufferedImage dst ) { 170 int width = src.getWidth(); 171 int height = src.getHeight(); 172 173 if ( dst == null ) 174 dst = createCompatibleDestImage( src, null ); 175 176 int[] inPixels = new int[width*height]; 177 int[] outPixels = new int[width*height]; 178 getRGB( src, 0, 0, width, height, inPixels ); 179 180 if ( premultiplyAlpha ) 181 ImageMath.premultiply( inPixels, 0, inPixels.length ); 182 convolve(kernel, inPixels, outPixels, width, height, alpha, edgeAction); 183 if ( premultiplyAlpha ) 184 ImageMath.unpremultiply( outPixels, 0, outPixels.length ); 185 186 setRGB( dst, 0, 0, width, height, outPixels ); 187 return dst; 188 } 189 190 public BufferedImage createCompatibleDestImage(BufferedImage src, ColorModel dstCM) { 191 if ( dstCM == null ) 192 dstCM = src.getColorModel(); 193 return new BufferedImage(dstCM, dstCM.createCompatibleWritableRaster(src.getWidth(), src.getHeight()), dstCM.isAlphaPremultiplied(), null); 194 } 195 196 public Rectangle2D getBounds2D( BufferedImage src ) { 197 return new Rectangle(0, 0, src.getWidth(), src.getHeight()); 198 } 199 200 public Point2D getPoint2D( Point2D srcPt, Point2D dstPt ) { 201 if ( dstPt == null ) 202 dstPt = new Point2D.Double(); 203 dstPt.setLocation( srcPt.getX(), srcPt.getY() ); 204 return dstPt; 205 } 206 207 public RenderingHints getRenderingHints() { 208 return null; 209 } 210 211 /** 212 * Convolve a block of pixels. 213 * @param kernel the kernel 214 * @param inPixels the input pixels 215 * @param outPixels the output pixels 216 * @param width the width 217 * @param height the height 218 * @param edgeAction what to do at the edges 219 */ 220 public static void convolve(Kernel kernel, int[] inPixels, int[] outPixels, int width, int height, int edgeAction) { 221 convolve(kernel, inPixels, outPixels, width, height, true, edgeAction); 222 } 223 224 /** 225 * Convolve a block of pixels. 226 * @param kernel the kernel 227 * @param inPixels the input pixels 228 * @param outPixels the output pixels 229 * @param width the width 230 * @param height the height 231 * @param alpha include alpha channel 232 * @param edgeAction what to do at the edges 233 */ 234 public static void convolve(Kernel kernel, int[] inPixels, int[] outPixels, int width, int height, boolean alpha, int edgeAction) { 235 if (kernel.getHeight() == 1) 236 convolveH(kernel, inPixels, outPixels, width, height, alpha, edgeAction); 237 else if (kernel.getWidth() == 1) 238 convolveV(kernel, inPixels, outPixels, width, height, alpha, edgeAction); 239 else 240 convolveHV(kernel, inPixels, outPixels, width, height, alpha, edgeAction); 241 } 242 243 /** 244 * Convolve with a 2D kernel. 245 * @param kernel the kernel 246 * @param inPixels the input pixels 247 * @param outPixels the output pixels 248 * @param width the width 249 * @param height the height 250 * @param alpha include alpha channel 251 * @param edgeAction what to do at the edges 252 */ 253 public static void convolveHV(Kernel kernel, int[] inPixels, int[] outPixels, int width, int height, boolean alpha, int edgeAction) { 254 int index = 0; 255 float[] matrix = kernel.getKernelData( null ); 256 int rows = kernel.getHeight(); 257 int cols = kernel.getWidth(); 258 int rows2 = rows/2; 259 int cols2 = cols/2; 260 261 for (int y = 0; y < height; y++) { 262 for (int x = 0; x < width; x++) { 263 float r = 0, g = 0, b = 0, a = 0; 264 265 for (int row = -rows2; row <= rows2; row++) { 266 int iy = y+row; 267 int ioffset; 268 if (0 <= iy && iy < height) 269 ioffset = iy*width; 270 else if ( edgeAction == CLAMP_EDGES ) 271 ioffset = y*width; 272 else if ( edgeAction == WRAP_EDGES ) 273 ioffset = ((iy+height) % height) * width; 274 else 275 continue; 276 int moffset = cols*(row+rows2)+cols2; 277 for (int col = -cols2; col <= cols2; col++) { 278 float f = matrix[moffset+col]; 279 280 if (f != 0) { 281 int ix = x+col; 282 if (!(0 <= ix && ix < width)) { 283 if ( edgeAction == CLAMP_EDGES ) 284 ix = x; 285 else if ( edgeAction == WRAP_EDGES ) 286 ix = (x+width) % width; 287 else 288 continue; 289 } 290 int rgb = inPixels[ioffset+ix]; 291 a += f * ((rgb >> 24) & 0xff); 292 r += f * ((rgb >> 16) & 0xff); 293 g += f * ((rgb >> 8) & 0xff); 294 b += f * (rgb & 0xff); 295 } 296 } 297 } 298 int ia = alpha ? PixelUtils.clamp((int)(a+0.5)) : 0xff; 299 int ir = PixelUtils.clamp((int)(r+0.5)); 300 int ig = PixelUtils.clamp((int)(g+0.5)); 301 int ib = PixelUtils.clamp((int)(b+0.5)); 302 outPixels[index++] = (ia << 24) | (ir << 16) | (ig << 8) | ib; 303 } 304 } 305 } 306 307 /** 308 * Convolve with a kernel consisting of one row. 309 * @param kernel the kernel 310 * @param inPixels the input pixels 311 * @param outPixels the output pixels 312 * @param width the width 313 * @param height the height 314 * @param alpha include alpha channel 315 * @param edgeAction what to do at the edges 316 */ 317 public static void convolveH(Kernel kernel, int[] inPixels, int[] outPixels, int width, int height, boolean alpha, int edgeAction) { 318 int index = 0; 319 float[] matrix = kernel.getKernelData( null ); 320 int cols = kernel.getWidth(); 321 int cols2 = cols/2; 322 323 for (int y = 0; y < height; y++) { 324 int ioffset = y*width; 325 for (int x = 0; x < width; x++) { 326 float r = 0, g = 0, b = 0, a = 0; 327 int moffset = cols2; 328 for (int col = -cols2; col <= cols2; col++) { 329 float f = matrix[moffset+col]; 330 331 if (f != 0) { 332 int ix = x+col; 333 if ( ix < 0 ) { 334 if ( edgeAction == CLAMP_EDGES ) 335 ix = 0; 336 else if ( edgeAction == WRAP_EDGES ) 337 ix = (x+width) % width; 338 } else if ( ix >= width) { 339 if ( edgeAction == CLAMP_EDGES ) 340 ix = width-1; 341 else if ( edgeAction == WRAP_EDGES ) 342 ix = (x+width) % width; 343 } 344 int rgb = inPixels[ioffset+ix]; 345 a += f * ((rgb >> 24) & 0xff); 346 r += f * ((rgb >> 16) & 0xff); 347 g += f * ((rgb >> 8) & 0xff); 348 b += f * (rgb & 0xff); 349 } 350 } 351 int ia = alpha ? PixelUtils.clamp((int)(a+0.5)) : 0xff; 352 int ir = PixelUtils.clamp((int)(r+0.5)); 353 int ig = PixelUtils.clamp((int)(g+0.5)); 354 int ib = PixelUtils.clamp((int)(b+0.5)); 355 outPixels[index++] = (ia << 24) | (ir << 16) | (ig << 8) | ib; 356 } 357 } 358 } 359 360 /** 361 * Convolve with a kernel consisting of one column. 362 * @param kernel the kernel 363 * @param inPixels the input pixels 364 * @param outPixels the output pixels 365 * @param width the width 366 * @param height the height 367 * @param alpha include alpha channel 368 * @param edgeAction what to do at the edges 369 */ 370 public static void convolveV(Kernel kernel, int[] inPixels, int[] outPixels, int width, int height, boolean alpha, int edgeAction) { 371 int index = 0; 372 float[] matrix = kernel.getKernelData( null ); 373 int rows = kernel.getHeight(); 374 int rows2 = rows/2; 375 376 for (int y = 0; y < height; y++) { 377 for (int x = 0; x < width; x++) { 378 float r = 0, g = 0, b = 0, a = 0; 379 380 for (int row = -rows2; row <= rows2; row++) { 381 int iy = y+row; 382 int ioffset; 383 if ( iy < 0 ) { 384 if ( edgeAction == CLAMP_EDGES ) 385 ioffset = 0; 386 else if ( edgeAction == WRAP_EDGES ) 387 ioffset = ((y+height) % height)*width; 388 else 389 ioffset = iy*width; 390 } else if ( iy >= height) { 391 if ( edgeAction == CLAMP_EDGES ) 392 ioffset = (height-1)*width; 393 else if ( edgeAction == WRAP_EDGES ) 394 ioffset = ((y+height) % height)*width; 395 else 396 ioffset = iy*width; 397 } else 398 ioffset = iy*width; 399 400 float f = matrix[row+rows2]; 401 402 if (f != 0) { 403 int rgb = inPixels[ioffset+x]; 404 a += f * ((rgb >> 24) & 0xff); 405 r += f * ((rgb >> 16) & 0xff); 406 g += f * ((rgb >> 8) & 0xff); 407 b += f * (rgb & 0xff); 408 } 409 } 410 int ia = alpha ? PixelUtils.clamp((int)(a+0.5)) : 0xff; 411 int ir = PixelUtils.clamp((int)(r+0.5)); 412 int ig = PixelUtils.clamp((int)(g+0.5)); 413 int ib = PixelUtils.clamp((int)(b+0.5)); 414 outPixels[index++] = (ia << 24) | (ir << 16) | (ig << 8) | ib; 415 } 416 } 417 } 418 419 public String toString() { 420 return "Blur/Convolve..."; 421 } 422}