001/** 002 * 003 * Copyright (c) 2014, the Railo Company Ltd. All rights reserved. 004 * 005 * This library is free software; you can redistribute it and/or 006 * modify it under the terms of the GNU Lesser General Public 007 * License as published by the Free Software Foundation; either 008 * version 2.1 of the License, or (at your option) any later version. 009 * 010 * This library is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013 * Lesser General Public License for more details. 014 * 015 * You should have received a copy of the GNU Lesser General Public 016 * License along with this library. If not, see <http://www.gnu.org/licenses/>. 017 * 018 **/ 019/* 020* 021 022Licensed under the Apache License, Version 2.0 (the "License"); 023you may not use this file except in compliance with the License. 024You may obtain a copy of the License at 025 026 http://www.apache.org/licenses/LICENSE-2.0 027 028Unless required by applicable law or agreed to in writing, software 029distributed under the License is distributed on an "AS IS" BASIS, 030WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 031See the License for the specific language governing permissions and 032limitations under the License. 033*/ 034 035package lucee.runtime.img.filter;import java.awt.image.BufferedImage; 036 037import lucee.runtime.engine.ThreadLocalPageContext; 038import lucee.runtime.exp.FunctionException; 039import lucee.runtime.exp.PageException; 040import lucee.runtime.img.ImageUtil; 041import lucee.runtime.type.KeyImpl; 042import lucee.runtime.type.Struct; 043import lucee.runtime.type.util.CollectionUtil; 044/** 045 * A filter which performs a box blur on an image. The horizontal and vertical blurs can be specified separately 046 * and a number of iterations can be given which allows an approximation to Gaussian blur. 047 */ 048public class BoxBlurFilter extends AbstractBufferedImageOp implements DynFiltering { 049 050 private float hRadius; 051 private float vRadius; 052 private int iterations = 1; 053 private boolean premultiplyAlpha = true; 054 055 /** 056 * Construct a default BoxBlurFilter. 057 */ 058 public BoxBlurFilter() { 059 } 060 061 /** 062 * Construct a BoxBlurFilter. 063 * @param hRadius the horizontal radius of blur 064 * @param vRadius the vertical radius of blur 065 * @param iterations the number of time to iterate the blur 066 */ 067 public BoxBlurFilter( float hRadius, float vRadius, int iterations ) { 068 this.hRadius = hRadius; 069 this.vRadius = vRadius; 070 this.iterations = iterations; 071 } 072 073 /** 074 * Set whether to premultiply the alpha channel. 075 * @param premultiplyAlpha true to premultiply the alpha 076 * @see #getPremultiplyAlpha 077 */ 078 public void setPremultiplyAlpha( boolean premultiplyAlpha ) { 079 this.premultiplyAlpha = premultiplyAlpha; 080 } 081 082 /** 083 * Get whether to premultiply the alpha channel. 084 * @return true to premultiply the alpha 085 * @see #setPremultiplyAlpha 086 */ 087 public boolean getPremultiplyAlpha() { 088 return premultiplyAlpha; 089 } 090 091 public BufferedImage filter( BufferedImage src, BufferedImage dst ) { 092 int width = src.getWidth(); 093 int height = src.getHeight(); 094 095 if ( dst == null ) 096 dst = createCompatibleDestImage( src, null ); 097 098 int[] inPixels = new int[width*height]; 099 int[] outPixels = new int[width*height]; 100 getRGB( src, 0, 0, width, height, inPixels ); 101 102 if ( premultiplyAlpha ) 103 ImageMath.premultiply( inPixels, 0, inPixels.length ); 104 for (int i = 0; i < iterations; i++ ) { 105 blur( inPixels, outPixels, width, height, hRadius ); 106 blur( outPixels, inPixels, height, width, vRadius ); 107 } 108 blurFractional( inPixels, outPixels, width, height, hRadius ); 109 blurFractional( outPixels, inPixels, height, width, vRadius ); 110 if ( premultiplyAlpha ) 111 ImageMath.unpremultiply( inPixels, 0, inPixels.length ); 112 113 setRGB( dst, 0, 0, width, height, inPixels ); 114 return dst; 115 } 116 117 /** 118 * Blur and transpose a block of ARGB pixels. 119 * @param in the input pixels 120 * @param out the output pixels 121 * @param width the width of the pixel array 122 * @param height the height of the pixel array 123 * @param radius the radius of blur 124 */ 125 public static void blur( int[] in, int[] out, int width, int height, float radius ) { 126 int widthMinus1 = width-1; 127 int r = (int)radius; 128 int tableSize = 2*r+1; 129 int divide[] = new int[256*tableSize]; 130 131 for ( int i = 0; i < 256*tableSize; i++ ) 132 divide[i] = i/tableSize; 133 134 int inIndex = 0; 135 136 for ( int y = 0; y < height; y++ ) { 137 int outIndex = y; 138 int ta = 0, tr = 0, tg = 0, tb = 0; 139 140 for ( int i = -r; i <= r; i++ ) { 141 int rgb = in[inIndex + ImageMath.clamp(i, 0, width-1)]; 142 ta += (rgb >> 24) & 0xff; 143 tr += (rgb >> 16) & 0xff; 144 tg += (rgb >> 8) & 0xff; 145 tb += rgb & 0xff; 146 } 147 148 for ( int x = 0; x < width; x++ ) { 149 out[ outIndex ] = (divide[ta] << 24) | (divide[tr] << 16) | (divide[tg] << 8) | divide[tb]; 150 151 int i1 = x+r+1; 152 if ( i1 > widthMinus1 ) 153 i1 = widthMinus1; 154 int i2 = x-r; 155 if ( i2 < 0 ) 156 i2 = 0; 157 int rgb1 = in[inIndex+i1]; 158 int rgb2 = in[inIndex+i2]; 159 160 ta += ((rgb1 >> 24) & 0xff)-((rgb2 >> 24) & 0xff); 161 tr += ((rgb1 & 0xff0000)-(rgb2 & 0xff0000)) >> 16; 162 tg += ((rgb1 & 0xff00)-(rgb2 & 0xff00)) >> 8; 163 tb += (rgb1 & 0xff)-(rgb2 & 0xff); 164 outIndex += height; 165 } 166 inIndex += width; 167 } 168 } 169 170 public static void blurFractional( int[] in, int[] out, int width, int height, float radius ) { 171 radius -= (int)radius; 172 float f = 1.0f/(1+2*radius); 173 int inIndex = 0; 174 175 for ( int y = 0; y < height; y++ ) { 176 int outIndex = y; 177 178 out[ outIndex ] = in[0]; 179 outIndex += height; 180 for ( int x = 1; x < width-1; x++ ) { 181 int i = inIndex+x; 182 int rgb1 = in[i-1]; 183 int rgb2 = in[i]; 184 int rgb3 = in[i+1]; 185 186 int a1 = (rgb1 >> 24) & 0xff; 187 int r1 = (rgb1 >> 16) & 0xff; 188 int g1 = (rgb1 >> 8) & 0xff; 189 int b1 = rgb1 & 0xff; 190 int a2 = (rgb2 >> 24) & 0xff; 191 int r2 = (rgb2 >> 16) & 0xff; 192 int g2 = (rgb2 >> 8) & 0xff; 193 int b2 = rgb2 & 0xff; 194 int a3 = (rgb3 >> 24) & 0xff; 195 int r3 = (rgb3 >> 16) & 0xff; 196 int g3 = (rgb3 >> 8) & 0xff; 197 int b3 = rgb3 & 0xff; 198 a1 = a2 + (int)((a1 + a3) * radius); 199 r1 = r2 + (int)((r1 + r3) * radius); 200 g1 = g2 + (int)((g1 + g3) * radius); 201 b1 = b2 + (int)((b1 + b3) * radius); 202 a1 *= f; 203 r1 *= f; 204 g1 *= f; 205 b1 *= f; 206 out[ outIndex ] = (a1 << 24) | (r1 << 16) | (g1 << 8) | b1; 207 outIndex += height; 208 } 209 out[ outIndex ] = in[width-1]; 210 inIndex += width; 211 } 212 } 213 214 /** 215 * Set the horizontal size of the blur. 216 * @param hRadius the radius of the blur in the horizontal direction 217 * @min-value 0 218 * @see #getHRadius 219 */ 220 public void setHRadius(float hRadius) { 221 this.hRadius = hRadius; 222 } 223 224 /** 225 * Get the horizontal size of the blur. 226 * @return the radius of the blur in the horizontal direction 227 * @see #setHRadius 228 */ 229 public float getHRadius() { 230 return hRadius; 231 } 232 233 /** 234 * Set the vertical size of the blur. 235 * @param vRadius the radius of the blur in the vertical direction 236 * @min-value 0 237 * @see #getVRadius 238 */ 239 public void setVRadius(float vRadius) { 240 this.vRadius = vRadius; 241 } 242 243 /** 244 * Get the vertical size of the blur. 245 * @return the radius of the blur in the vertical direction 246 * @see #setVRadius 247 */ 248 public float getVRadius() { 249 return vRadius; 250 } 251 252 /** 253 * Set both the horizontal and vertical sizes of the blur. 254 * @param radius the radius of the blur in both directions 255 * @min-value 0 256 * @see #getRadius 257 */ 258 public void setRadius(float radius) { 259 this.hRadius = this.vRadius = radius; 260 } 261 262 /** 263 * Get the size of the blur. 264 * @return the radius of the blur in the horizontal direction 265 * @see #setRadius 266 */ 267 public float getRadius() { 268 return hRadius; 269 } 270 271 /** 272 * Set the number of iterations the blur is performed. 273 * @param iterations the number of iterations 274 * @min-value 0 275 * @see #getIterations 276 */ 277 public void setIterations(int iterations) { 278 this.iterations = iterations; 279 } 280 281 /** 282 * Get the number of iterations the blur is performed. 283 * @return the number of iterations 284 * @see #setIterations 285 */ 286 public int getIterations() { 287 return iterations; 288 } 289 290 public String toString() { 291 return "Blur/Box Blur..."; 292 } 293 public BufferedImage filter(BufferedImage src, Struct parameters) throws PageException {BufferedImage dst=ImageUtil.createBufferedImage(src); 294 Object o; 295 if((o=parameters.removeEL(KeyImpl.init("PremultiplyAlpha")))!=null)setPremultiplyAlpha(ImageFilterUtil.toBooleanValue(o,"PremultiplyAlpha")); 296 if((o=parameters.removeEL(KeyImpl.init("Iterations")))!=null)setIterations(ImageFilterUtil.toIntValue(o,"Iterations")); 297 if((o=parameters.removeEL(KeyImpl.init("HRadius")))!=null)setHRadius(ImageFilterUtil.toFloatValue(o,"HRadius")); 298 if((o=parameters.removeEL(KeyImpl.init("VRadius")))!=null)setVRadius(ImageFilterUtil.toFloatValue(o,"VRadius")); 299 if((o=parameters.removeEL(KeyImpl.init("Radius")))!=null)setRadius(ImageFilterUtil.toFloatValue(o,"Radius")); 300 301 // check for arguments not supported 302 if(parameters.size()>0) { 303 throw new FunctionException(ThreadLocalPageContext.get(), "ImageFilter", 3, "parameters", "the parameter"+(parameters.size()>1?"s":"")+" ["+CollectionUtil.getKeyList(parameters,", ")+"] "+(parameters.size()>1?"are":"is")+" not allowed, only the following parameters are supported [PremultiplyAlpha, Iterations, HRadius, VRadius, Radius]"); 304 } 305 306 return filter(src, dst); 307 } 308}