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.image.BufferedImage; 018 import java.awt.image.Kernel; 019 020 import railo.runtime.engine.ThreadLocalPageContext; 021 import railo.runtime.exp.FunctionException; 022 import railo.runtime.exp.PageException; 023 import railo.runtime.img.ImageUtil; 024 import railo.runtime.type.KeyImpl; 025 import railo.runtime.type.Struct; 026 import railo.runtime.type.util.CollectionUtil; 027 028 /** 029 * A filter which applies Gaussian blur to an image. This is a subclass of ConvolveFilter 030 * which simply creates a kernel with a Gaussian distribution for blurring. 031 * 032 */ 033 public class GaussianFilter extends ConvolveFilter implements DynFiltering { 034 035 /** 036 * The blur radius. 037 */ 038 protected float radius; 039 040 /** 041 * The convolution kernel. 042 */ 043 protected Kernel kernel; 044 045 /** 046 * Construct a Gaussian filter. 047 */ 048 public GaussianFilter() { 049 this(2); 050 } 051 052 /** 053 * Construct a Gaussian filter. 054 * @param radius blur radius in pixels 055 */ 056 public GaussianFilter(float radius) { 057 setRadius(radius); 058 } 059 060 /** 061 * Set the radius of the kernel, and hence the amount of blur. The bigger the radius, the longer this filter will take. 062 * @param radius the radius of the blur in pixels. 063 * @min-value 0 064 * @max-value 100+ 065 * @see #getRadius 066 */ 067 public void setRadius(float radius) { 068 this.radius = radius; 069 kernel = makeKernel(radius); 070 } 071 072 /** 073 * Get the radius of the kernel. 074 * @return the radius 075 * @see #setRadius 076 */ 077 public float getRadius() { 078 return radius; 079 } 080 081 public BufferedImage filter( BufferedImage src, BufferedImage dst ) { 082 int width = src.getWidth(); 083 int height = src.getHeight(); 084 085 if ( dst == null ) 086 dst = createCompatibleDestImage( src, null ); 087 088 int[] inPixels = new int[width*height]; 089 int[] outPixels = new int[width*height]; 090 src.getRGB( 0, 0, width, height, inPixels, 0, width ); 091 092 if ( radius > 0 ) { 093 convolveAndTranspose(kernel, inPixels, outPixels, width, height, alpha, alpha && premultiplyAlpha, false, CLAMP_EDGES); 094 convolveAndTranspose(kernel, outPixels, inPixels, height, width, alpha, false, alpha && premultiplyAlpha, CLAMP_EDGES); 095 } 096 097 dst.setRGB( 0, 0, width, height, inPixels, 0, width ); 098 return dst; 099 } 100 101 /** 102 * Blur and transpose a block of ARGB pixels. 103 * @param kernel the blur kernel 104 * @param inPixels the input pixels 105 * @param outPixels the output pixels 106 * @param width the width of the pixel array 107 * @param height the height of the pixel array 108 * @param alpha whether to blur the alpha channel 109 * @param edgeAction what to do at the edges 110 */ 111 public static void convolveAndTranspose(Kernel kernel, int[] inPixels, int[] outPixels, int width, int height, boolean alpha, boolean premultiply, boolean unpremultiply, int edgeAction) { 112 float[] matrix = kernel.getKernelData( null ); 113 int cols = kernel.getWidth(); 114 int cols2 = cols/2; 115 116 for (int y = 0; y < height; y++) { 117 int index = y; 118 int ioffset = y*width; 119 for (int x = 0; x < width; x++) { 120 float r = 0, g = 0, b = 0, a = 0; 121 int moffset = cols2; 122 for (int col = -cols2; col <= cols2; col++) { 123 float f = matrix[moffset+col]; 124 125 if (f != 0) { 126 int ix = x+col; 127 if ( ix < 0 ) { 128 if ( edgeAction == CLAMP_EDGES ) 129 ix = 0; 130 else if ( edgeAction == WRAP_EDGES ) 131 ix = (x+width) % width; 132 } else if ( ix >= width) { 133 if ( edgeAction == CLAMP_EDGES ) 134 ix = width-1; 135 else if ( edgeAction == WRAP_EDGES ) 136 ix = (x+width) % width; 137 } 138 int rgb = inPixels[ioffset+ix]; 139 int pa = (rgb >> 24) & 0xff; 140 int pr = (rgb >> 16) & 0xff; 141 int pg = (rgb >> 8) & 0xff; 142 int pb = rgb & 0xff; 143 if ( premultiply ) { 144 float a255 = pa * (1.0f / 255.0f); 145 pr *= a255; 146 pg *= a255; 147 pb *= a255; 148 } 149 a += f * pa; 150 r += f * pr; 151 g += f * pg; 152 b += f * pb; 153 } 154 } 155 if ( unpremultiply && a != 0 && a != 255 ) { 156 float f = 255.0f / a; 157 r *= f; 158 g *= f; 159 b *= f; 160 } 161 int ia = alpha ? PixelUtils.clamp((int)(a+0.5)) : 0xff; 162 int ir = PixelUtils.clamp((int)(r+0.5)); 163 int ig = PixelUtils.clamp((int)(g+0.5)); 164 int ib = PixelUtils.clamp((int)(b+0.5)); 165 outPixels[index] = (ia << 24) | (ir << 16) | (ig << 8) | ib; 166 index += height; 167 } 168 } 169 } 170 171 /** 172 * Make a Gaussian blur kernel. 173 * @param radius the blur radius 174 * @return the kernel 175 */ 176 public static Kernel makeKernel(float radius) { 177 int r = (int)Math.ceil(radius); 178 int rows = r*2+1; 179 float[] matrix = new float[rows]; 180 float sigma = radius/3; 181 float sigma22 = 2*sigma*sigma; 182 float sigmaPi2 = 2*ImageMath.PI*sigma; 183 float sqrtSigmaPi2 = (float)Math.sqrt(sigmaPi2); 184 float radius2 = radius*radius; 185 float total = 0; 186 int index = 0; 187 for (int row = -r; row <= r; row++) { 188 float distance = row*row; 189 if (distance > radius2) 190 matrix[index] = 0; 191 else 192 matrix[index] = (float)Math.exp(-(distance)/sigma22) / sqrtSigmaPi2; 193 total += matrix[index]; 194 index++; 195 } 196 for (int i = 0; i < rows; i++) 197 matrix[i] /= total; 198 199 return new Kernel(rows, 1, matrix); 200 } 201 202 public String toString() { 203 return "Blur/Gaussian Blur..."; 204 } 205 public BufferedImage filter(BufferedImage src, Struct parameters) throws PageException {BufferedImage dst=ImageUtil.createBufferedImage(src); 206 Object o; 207 if((o=parameters.removeEL(KeyImpl.init("Radius")))!=null)setRadius(ImageFilterUtil.toFloatValue(o,"Radius")); 208 if((o=parameters.removeEL(KeyImpl.init("EdgeAction")))!=null)setEdgeAction(ImageFilterUtil.toString(o,"EdgeAction")); 209 if((o=parameters.removeEL(KeyImpl.init("UseAlpha")))!=null)setUseAlpha(ImageFilterUtil.toBooleanValue(o,"UseAlpha")); 210 if((o=parameters.removeEL(KeyImpl.init("PremultiplyAlpha")))!=null)setPremultiplyAlpha(ImageFilterUtil.toBooleanValue(o,"PremultiplyAlpha")); 211 212 // check for arguments not supported 213 if(parameters.size()>0) { 214 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 [Radius, Kernel, EdgeAction, UseAlpha, PremultiplyAlpha]"); 215 } 216 217 return filter(src, dst); 218 } 219 }