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