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