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