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}