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.Rectangle; 036import java.awt.RenderingHints; 037import java.awt.geom.Point2D; 038import java.awt.geom.Rectangle2D; 039import java.awt.image.BufferedImage; 040import java.awt.image.ColorModel; 041 042import lucee.runtime.engine.ThreadLocalPageContext; 043import lucee.runtime.exp.FunctionException; 044import lucee.runtime.exp.PageException; 045import lucee.runtime.img.ImageUtil; 046import lucee.runtime.type.KeyImpl; 047import lucee.runtime.type.Struct; 048import lucee.runtime.type.util.CollectionUtil; 049 050/** 051 * A filter which performs a box blur with a different blur radius at each pixel. The radius can either be specified by 052 * providing a blur mask image or by overriding the blurRadiusAt method. 053 */ 054public class VariableBlurFilter extends AbstractBufferedImageOp implements DynFiltering { 055 056 private int hRadius = 1; 057 private int vRadius = 1; 058 private int iterations = 1; 059 private BufferedImage blurMask; 060 private boolean premultiplyAlpha = true; 061 062 /** 063 * Set whether to premultiply the alpha channel. 064 * @param premultiplyAlpha true to premultiply the alpha 065 * @see #getPremultiplyAlpha 066 */ 067 public void setPremultiplyAlpha( boolean premultiplyAlpha ) { 068 this.premultiplyAlpha = premultiplyAlpha; 069 } 070 071 /** 072 * Get whether to premultiply the alpha channel. 073 * @return true to premultiply the alpha 074 * @see #setPremultiplyAlpha 075 */ 076 public boolean getPremultiplyAlpha() { 077 return premultiplyAlpha; 078 } 079 080 public BufferedImage filter( BufferedImage src, BufferedImage dst ) { 081 int width = src.getWidth(); 082 int height = src.getHeight(); 083 084 if ( dst == null ) 085 dst = new BufferedImage( width, height, BufferedImage.TYPE_INT_ARGB ); 086 087 int[] inPixels = new int[width*height]; 088 int[] outPixels = new int[width*height]; 089 getRGB( src, 0, 0, width, height, inPixels ); 090 091 if ( premultiplyAlpha ) 092 ImageMath.premultiply( inPixels, 0, inPixels.length ); 093 for (int i = 0; i < iterations; i++ ) { 094 blur( inPixels, outPixels, width, height, hRadius, 1 ); 095 blur( outPixels, inPixels, height, width, vRadius, 2 ); 096 } 097 if ( premultiplyAlpha ) 098 ImageMath.unpremultiply( inPixels, 0, inPixels.length ); 099 100 setRGB( dst, 0, 0, width, height, inPixels ); 101 return dst; 102 } 103 104 public BufferedImage createCompatibleDestImage(BufferedImage src, ColorModel dstCM) { 105 if ( dstCM == null ) 106 dstCM = src.getColorModel(); 107 return new BufferedImage(dstCM, dstCM.createCompatibleWritableRaster(src.getWidth(), src.getHeight()), dstCM.isAlphaPremultiplied(), null); 108 } 109 110 public Rectangle2D getBounds2D( BufferedImage src ) { 111 return new Rectangle(0, 0, src.getWidth(), src.getHeight()); 112 } 113 114 public Point2D getPoint2D( Point2D srcPt, Point2D dstPt ) { 115 if ( dstPt == null ) 116 dstPt = new Point2D.Double(); 117 dstPt.setLocation( srcPt.getX(), srcPt.getY() ); 118 return dstPt; 119 } 120 121 public RenderingHints getRenderingHints() { 122 return null; 123 } 124 125 public void blur( int[] in, int[] out, int width, int height, int radius, int pass ) { 126 int widthMinus1 = width-1; 127 int[] r = new int[width]; 128 int[] g = new int[width]; 129 int[] b = new int[width]; 130 int[] a = new int[width]; 131 int[] mask = new int[width]; 132 133 int inIndex = 0; 134 135 for ( int y = 0; y < height; y++ ) { 136 int outIndex = y; 137 138 if ( blurMask != null ) { 139 if ( pass == 1 ) 140 getRGB( blurMask, 0, y, width, 1, mask ); 141 else 142 getRGB( blurMask, y, 0, 1, width, mask ); 143 } 144 145 for ( int x = 0; x < width; x++ ) { 146 int argb = in[inIndex+x]; 147 a[x] = (argb >> 24) & 0xff; 148 r[x] = (argb >> 16) & 0xff; 149 g[x] = (argb >> 8) & 0xff; 150 b[x] = argb & 0xff; 151 if ( x != 0 ) { 152 a[x] += a[x-1]; 153 r[x] += r[x-1]; 154 g[x] += g[x-1]; 155 b[x] += b[x-1]; 156 } 157 } 158 159 for ( int x = 0; x < width; x++ ) { 160 // Get the blur radius at x, y 161 int ra; 162 if ( blurMask != null ) { 163 if ( pass == 1 ) 164 ra = (int)((mask[x] & 0xff)*hRadius/255f); 165 else 166 ra = (int)((mask[x] & 0xff)*vRadius/255f); 167 } else { 168 if ( pass == 1 ) 169 ra = (int)(blurRadiusAt( x, y, width, height ) * hRadius); 170 else 171 ra = (int)(blurRadiusAt( y, x, height, width ) * vRadius); 172 } 173 174 int divisor = 2*ra+1; 175 int ta = 0, tr = 0, tg = 0, tb = 0; 176 int i1 = x+ra; 177 if ( i1 > widthMinus1 ) { 178 int f = i1-widthMinus1; 179 int l = widthMinus1; 180 ta += (a[l]-a[l-1]) * f; 181 tr += (r[l]-r[l-1]) * f; 182 tg += (g[l]-g[l-1]) * f; 183 tb += (b[l]-b[l-1]) * f; 184 i1 = widthMinus1; 185 } 186 int i2 = x-ra-1; 187 if ( i2 < 0 ) { 188 ta -= a[0] * i2; 189 tr -= r[0] * i2; 190 tg -= g[0] * i2; 191 tb -= b[0] * i2; 192 i2 = 0; 193 } 194 195 ta += a[i1] - a[i2]; 196 tr += r[i1] - r[i2]; 197 tg += g[i1] - g[i2]; 198 tb += b[i1] - b[i2]; 199 out[ outIndex ] = ((ta/divisor) << 24) | ((tr/divisor) << 16) | ((tg/divisor) << 8) | (tb/divisor); 200 201 outIndex += height; 202 } 203 inIndex += width; 204 } 205 } 206 207 /** 208 * Override this to get a different blur radius at eahc point. 209 * @param x the x coordinate 210 * @param y the y coordinate 211 * @param width the width of the image 212 * @param height the height of the image 213 * @return the blur radius 214 */ 215 protected float blurRadiusAt( int x, int y, int width, int height ) { 216 return (float)x/width; 217 } 218 219 /** 220 * Set the horizontal size of the blur. 221 * @param hRadius the radius of the blur in the horizontal direction 222 * @min-value 0 223 * @see #getHRadius 224 */ 225 public void setHRadius(int hRadius) { 226 this.hRadius = hRadius; 227 } 228 229 /** 230 * Get the horizontal size of the blur. 231 * @return the radius of the blur in the horizontal direction 232 * @see #setHRadius 233 */ 234 public int getHRadius() { 235 return hRadius; 236 } 237 238 /** 239 * Set the vertical size of the blur. 240 * @param vRadius the radius of the blur in the vertical direction 241 * @min-value 0 242 * @see #getVRadius 243 */ 244 public void setVRadius(int vRadius) { 245 this.vRadius = vRadius; 246 } 247 248 /** 249 * Get the vertical size of the blur. 250 * @return the radius of the blur in the vertical direction 251 * @see #setVRadius 252 */ 253 public int getVRadius() { 254 return vRadius; 255 } 256 257 /** 258 * Set the radius of the effect. 259 * @param radius the radius 260 * @min-value 0 261 * @see #getRadius 262 */ 263 public void setRadius(int radius) { 264 this.hRadius = this.vRadius = radius; 265 } 266 267 /** 268 * Get the radius of the effect. 269 * @return the radius 270 * @see #setRadius 271 */ 272 public int getRadius() { 273 return hRadius; 274 } 275 276 /** 277 * Set the number of iterations the blur is performed. 278 * @param iterations the number of iterations 279 * @min-value 0 280 * @see #getIterations 281 */ 282 public void setIterations(int iterations) { 283 this.iterations = iterations; 284 } 285 286 /** 287 * Get the number of iterations the blur is performed. 288 * @return the number of iterations 289 * @see #setIterations 290 */ 291 public int getIterations() { 292 return iterations; 293 } 294 295 /** 296 * Set the mask used to give the amount of blur at each point. 297 * @param blurMask the mask 298 * @see #getBlurMask 299 */ 300 public void setBlurMask(BufferedImage blurMask) { 301 this.blurMask = blurMask; 302 } 303 304 /** 305 * Get the mask used to give the amount of blur at each point. 306 * @return the mask 307 * @see #setBlurMask 308 */ 309 public BufferedImage getBlurMask() { 310 return blurMask; 311 } 312 313 public String toString() { 314 return "Blur/Variable Blur..."; 315 } 316 public BufferedImage filter(BufferedImage src, Struct parameters) throws PageException {BufferedImage dst=ImageUtil.createBufferedImage(src); 317 Object o; 318 if((o=parameters.removeEL(KeyImpl.init("PremultiplyAlpha")))!=null)setPremultiplyAlpha(ImageFilterUtil.toBooleanValue(o,"PremultiplyAlpha")); 319 if((o=parameters.removeEL(KeyImpl.init("Iterations")))!=null)setIterations(ImageFilterUtil.toIntValue(o,"Iterations")); 320 if((o=parameters.removeEL(KeyImpl.init("HRadius")))!=null)setHRadius(ImageFilterUtil.toIntValue(o,"HRadius")); 321 if((o=parameters.removeEL(KeyImpl.init("VRadius")))!=null)setVRadius(ImageFilterUtil.toIntValue(o,"VRadius")); 322 if((o=parameters.removeEL(KeyImpl.init("Radius")))!=null)setRadius(ImageFilterUtil.toIntValue(o,"Radius")); 323 if((o=parameters.removeEL(KeyImpl.init("BlurMask")))!=null)setBlurMask(ImageFilterUtil.toBufferedImage(o,"BlurMask")); 324 325 // check for arguments not supported 326 if(parameters.size()>0) { 327 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, BlurMask]"); 328 } 329 330 return filter(src, dst); 331 } 332}