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.Rectangle;
018    import java.awt.RenderingHints;
019    import java.awt.geom.Point2D;
020    import java.awt.geom.Rectangle2D;
021    import java.awt.image.BufferedImage;
022    import java.awt.image.ColorModel;
023    
024    import railo.runtime.engine.ThreadLocalPageContext;
025    import railo.runtime.exp.FunctionException;
026    import railo.runtime.exp.PageException;
027    import railo.runtime.img.ImageUtil;
028    import railo.runtime.type.KeyImpl;
029    import railo.runtime.type.List;
030    import railo.runtime.type.Struct;
031    
032    /**
033     * A filter which performs a box blur with a different blur radius at each pixel. The radius can either be specified by
034     * providing a blur mask image or by overriding the blurRadiusAt method.
035     */
036    public class VariableBlurFilter extends AbstractBufferedImageOp  implements DynFiltering {
037    
038            private int hRadius = 1;
039            private int vRadius = 1;
040            private int iterations = 1;
041            private BufferedImage blurMask;
042            private boolean premultiplyAlpha = true;
043    
044        /**
045         * Set whether to premultiply the alpha channel.
046         * @param premultiplyAlpha true to premultiply the alpha
047         * @see #getPremultiplyAlpha
048         */
049            public void setPremultiplyAlpha( boolean premultiplyAlpha ) {
050                    this.premultiplyAlpha = premultiplyAlpha;
051            }
052    
053        /**
054         * Get whether to premultiply the alpha channel.
055         * @return true to premultiply the alpha
056         * @see #setPremultiplyAlpha
057         */
058            public boolean getPremultiplyAlpha() {
059                    return premultiplyAlpha;
060            }
061    
062        public BufferedImage filter( BufferedImage src, BufferedImage dst ) {
063                    int width = src.getWidth();
064            int height = src.getHeight();
065    
066            if ( dst == null )
067                dst = new BufferedImage( width, height, BufferedImage.TYPE_INT_ARGB );
068    
069            int[] inPixels = new int[width*height];
070            int[] outPixels = new int[width*height];
071            getRGB( src, 0, 0, width, height, inPixels );
072    
073            if ( premultiplyAlpha )
074                            ImageMath.premultiply( inPixels, 0, inPixels.length );
075            for (int i = 0; i < iterations; i++ ) {
076                blur( inPixels, outPixels, width, height, hRadius, 1 );
077                blur( outPixels, inPixels, height, width, vRadius, 2 );
078            }
079            if ( premultiplyAlpha )
080                            ImageMath.unpremultiply( inPixels, 0, inPixels.length );
081    
082            setRGB( dst, 0, 0, width, height, inPixels );
083            return dst;
084        }
085    
086        public BufferedImage createCompatibleDestImage(BufferedImage src, ColorModel dstCM) {
087            if ( dstCM == null )
088                dstCM = src.getColorModel();
089            return new BufferedImage(dstCM, dstCM.createCompatibleWritableRaster(src.getWidth(), src.getHeight()), dstCM.isAlphaPremultiplied(), null);
090        }
091        
092        public Rectangle2D getBounds2D( BufferedImage src ) {
093            return new Rectangle(0, 0, src.getWidth(), src.getHeight());
094        }
095        
096        public Point2D getPoint2D( Point2D srcPt, Point2D dstPt ) {
097            if ( dstPt == null )
098                dstPt = new Point2D.Double();
099            dstPt.setLocation( srcPt.getX(), srcPt.getY() );
100            return dstPt;
101        }
102    
103        public RenderingHints getRenderingHints() {
104            return null;
105        }
106    
107        public void blur( int[] in, int[] out, int width, int height, int radius, int pass ) {
108            int widthMinus1 = width-1;
109            int[] r = new int[width];
110            int[] g = new int[width];
111            int[] b = new int[width];
112            int[] a = new int[width];
113            int[] mask = new int[width];
114    
115                    int inIndex = 0;
116    
117            for ( int y = 0; y < height; y++ ) {
118                int outIndex = y;
119    
120                            if ( blurMask != null ) {
121                                    if ( pass == 1 )
122                                            getRGB( blurMask, 0, y, width, 1, mask );
123                                    else
124                                            getRGB( blurMask, y, 0, 1, width, mask );
125                            }
126    
127                for ( int x = 0; x < width; x++ ) {
128                                    int argb = in[inIndex+x];
129                                    a[x] = (argb >> 24) & 0xff;
130                    r[x] = (argb >> 16) & 0xff;
131                    g[x] = (argb >> 8) & 0xff;
132                    b[x] = argb & 0xff;
133                    if ( x != 0 ) {
134                        a[x] += a[x-1];
135                        r[x] += r[x-1];
136                        g[x] += g[x-1];
137                        b[x] += b[x-1];
138                    }
139                            }
140    
141                for ( int x = 0; x < width; x++ ) {
142                                    // Get the blur radius at x, y
143                                    int ra;
144                                    if ( blurMask != null ) {
145                                            if ( pass == 1 )
146                                                    ra = (int)((mask[x] & 0xff)*hRadius/255f);
147                                            else
148                                                    ra = (int)((mask[x] & 0xff)*vRadius/255f);
149                                    } else {
150                                            if ( pass == 1 )
151                                                    ra = (int)(blurRadiusAt( x, y, width, height ) * hRadius);
152                                            else
153                                                    ra = (int)(blurRadiusAt( y, x, height, width ) * vRadius);
154                                    }
155    
156                    int divisor = 2*ra+1;
157                    int ta = 0, tr = 0, tg = 0, tb = 0;
158                                    int i1 = x+ra;
159                    if ( i1 > widthMinus1 ) {
160                        int f = i1-widthMinus1;
161                                            int l = widthMinus1;
162                                            ta += (a[l]-a[l-1]) * f;
163                                            tr += (r[l]-r[l-1]) * f;
164                                            tg += (g[l]-g[l-1]) * f;
165                                            tb += (b[l]-b[l-1]) * f;
166                                            i1 = widthMinus1;
167                    }
168                                    int i2 = x-ra-1;
169                    if ( i2 < 0 ) {
170                                            ta -= a[0] * i2;
171                                            tr -= r[0] * i2;
172                                            tg -= g[0] * i2;
173                                            tb -= b[0] * i2;
174                        i2 = 0;
175                                    }
176                    
177                    ta += a[i1] - a[i2];
178                    tr += r[i1] - r[i2];
179                    tg += g[i1] - g[i2];
180                    tb += b[i1] - b[i2];
181                    out[ outIndex ] = ((ta/divisor) << 24) | ((tr/divisor) << 16) | ((tg/divisor) << 8) | (tb/divisor);
182    
183                    outIndex += height;
184                }
185                            inIndex += width;
186            }
187        }
188        
189            /**
190         * Override this to get a different blur radius at eahc point.
191         * @param x the x coordinate
192         * @param y the y coordinate
193         * @param width the width of the image
194         * @param height the height of the image
195         * @return the blur radius
196         */
197            protected float blurRadiusAt( int x, int y, int width, int height ) {
198                    return (float)x/width;
199            }
200    
201            /**
202             * Set the horizontal size of the blur.
203             * @param hRadius the radius of the blur in the horizontal direction
204         * @min-value 0
205         * @see #getHRadius
206             */
207            public void setHRadius(int hRadius) {
208                    this.hRadius = hRadius;
209            }
210            
211            /**
212             * Get the horizontal size of the blur.
213             * @return the radius of the blur in the horizontal direction
214         * @see #setHRadius
215             */
216            public int getHRadius() {
217                    return hRadius;
218            }
219            
220            /**
221             * Set the vertical size of the blur.
222             * @param vRadius the radius of the blur in the vertical direction
223         * @min-value 0
224         * @see #getVRadius
225             */
226            public void setVRadius(int vRadius) {
227                    this.vRadius = vRadius;
228            }
229            
230            /**
231             * Get the vertical size of the blur.
232             * @return the radius of the blur in the vertical direction
233         * @see #setVRadius
234             */
235            public int getVRadius() {
236                    return vRadius;
237            }
238            
239            /**
240             * Set the radius of the effect.
241             * @param radius the radius
242         * @min-value 0
243         * @see #getRadius
244             */
245            public void setRadius(int radius) {
246                    this.hRadius = this.vRadius = radius;
247            }
248            
249            /**
250             * Get the radius of the effect.
251             * @return the radius
252         * @see #setRadius
253             */
254            public int getRadius() {
255                    return hRadius;
256            }
257            
258            /**
259             * Set the number of iterations the blur is performed.
260             * @param iterations the number of iterations
261         * @min-value 0
262         * @see #getIterations
263             */
264            public void setIterations(int iterations) {
265                    this.iterations = iterations;
266            }
267            
268            /**
269             * Get the number of iterations the blur is performed.
270             * @return the number of iterations
271         * @see #setIterations
272             */
273            public int getIterations() {
274                    return iterations;
275            }
276            
277            /**
278             * Set the mask used to give the amount of blur at each point.
279             * @param blurMask the mask
280         * @see #getBlurMask
281             */
282            public void setBlurMask(BufferedImage blurMask) {
283                    this.blurMask = blurMask;
284            }
285            
286            /**
287             * Get the mask used to give the amount of blur at each point.
288             * @return the mask
289         * @see #setBlurMask
290             */
291            public BufferedImage getBlurMask() {
292                    return blurMask;
293            }
294            
295            public String toString() {
296                    return "Blur/Variable Blur...";
297            }
298            public BufferedImage filter(BufferedImage src, Struct parameters) throws PageException {BufferedImage dst=ImageUtil.createBufferedImage(src);
299                    Object o;
300                    if((o=parameters.removeEL(KeyImpl.init("PremultiplyAlpha")))!=null)setPremultiplyAlpha(ImageFilterUtil.toBooleanValue(o,"PremultiplyAlpha"));
301                    if((o=parameters.removeEL(KeyImpl.init("Iterations")))!=null)setIterations(ImageFilterUtil.toIntValue(o,"Iterations"));
302                    if((o=parameters.removeEL(KeyImpl.init("HRadius")))!=null)setHRadius(ImageFilterUtil.toIntValue(o,"HRadius"));
303                    if((o=parameters.removeEL(KeyImpl.init("VRadius")))!=null)setVRadius(ImageFilterUtil.toIntValue(o,"VRadius"));
304                    if((o=parameters.removeEL(KeyImpl.init("Radius")))!=null)setRadius(ImageFilterUtil.toIntValue(o,"Radius"));
305                    if((o=parameters.removeEL(KeyImpl.init("BlurMask")))!=null)setBlurMask(ImageFilterUtil.toBufferedImage(o,"BlurMask"));
306    
307                    // check for arguments not supported
308                    if(parameters.size()>0) {
309                            throw new FunctionException(ThreadLocalPageContext.get(), "ImageFilter", 3, "parameters", "the parameter"+(parameters.size()>1?"s":"")+" ["+List.arrayToList(parameters.keysAsString(),", ")+"] "+(parameters.size()>1?"are":"is")+" not allowed, only the following parameters are supported [PremultiplyAlpha, Iterations, HRadius, VRadius, Radius, BlurMask]");
310                    }
311    
312                    return filter(src, dst);
313            }
314    }