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}