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}