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 performs a "smart blur". i.e. a blur which blurs smotth parts of the image while preserving edges.
030     */
031    public class SmartBlurFilter extends AbstractBufferedImageOp  implements DynFiltering {
032    
033            private int hRadius = 5;
034            private int vRadius = 5;
035            private int threshold = 10;
036            
037        public BufferedImage filter( BufferedImage src, BufferedImage dst ) {
038            int width = src.getWidth();
039            int height = src.getHeight();
040    
041            if ( dst == null )
042                dst = createCompatibleDestImage( src, null );
043    
044            int[] inPixels = new int[width*height];
045            int[] outPixels = new int[width*height];
046            getRGB( src, 0, 0, width, height, inPixels );
047    
048                    Kernel kernel = GaussianFilter.makeKernel(hRadius);
049                    thresholdBlur( kernel, inPixels, outPixels, width, height, true );
050                    thresholdBlur( kernel, outPixels, inPixels, height, width, true );
051    
052            setRGB( dst, 0, 0, width, height, inPixels );
053            return dst;
054        }
055    
056            /**
057             * Convolve with a kernel consisting of one row
058             */
059            private void thresholdBlur(Kernel kernel, int[] inPixels, int[] outPixels, int width, int height, boolean alpha) {
060                    //int index = 0;
061                    float[] matrix = kernel.getKernelData( null );
062                    int cols = kernel.getWidth();
063                    int cols2 = cols/2;
064    
065                    for (int y = 0; y < height; y++) {
066                            int ioffset = y*width;
067                int outIndex = y;
068                            for (int x = 0; x < width; x++) {
069                                    float r = 0, g = 0, b = 0, a = 0;
070                                    int moffset = cols2;
071    
072                    int rgb1 = inPixels[ioffset+x];
073                    int a1 = (rgb1 >> 24) & 0xff;
074                    int r1 = (rgb1 >> 16) & 0xff;
075                    int g1 = (rgb1 >> 8) & 0xff;
076                    int b1 = rgb1 & 0xff;
077                                    float af = 0, rf = 0, gf = 0, bf = 0;
078                    for (int col = -cols2; col <= cols2; col++) {
079                                            float f = matrix[moffset+col];
080    
081                                            if (f != 0) {
082                                                    int ix = x+col;
083                                                    if (!(0 <= ix && ix < width))
084                                                            ix = x;
085                                                    int rgb2 = inPixels[ioffset+ix];
086                            int a2 = (rgb2 >> 24) & 0xff;
087                            int r2 = (rgb2 >> 16) & 0xff;
088                            int g2 = (rgb2 >> 8) & 0xff;
089                            int b2 = rgb2 & 0xff;
090    
091                                                    int d;
092                            d = a1-a2;
093                            if ( d >= -threshold && d <= threshold ) {
094                                a += f * a2;
095                                af += f;
096                            }
097                            d = r1-r2;
098                            if ( d >= -threshold && d <= threshold ) {
099                                r += f * r2;
100                                rf += f;
101                            }
102                            d = g1-g2;
103                            if ( d >= -threshold && d <= threshold ) {
104                                g += f * g2;
105                                gf += f;
106                            }
107                            d = b1-b2;
108                            if ( d >= -threshold && d <= threshold ) {
109                                b += f * b2;
110                                bf += f;
111                            }
112                                            }
113                                    }
114                    a = af == 0 ? a1 : a/af;
115                    r = rf == 0 ? r1 : r/rf;
116                    g = gf == 0 ? g1 : g/gf;
117                    b = bf == 0 ? b1 : b/bf;
118                                    int ia = alpha ? PixelUtils.clamp((int)(a+0.5)) : 0xff;
119                                    int ir = PixelUtils.clamp((int)(r+0.5));
120                                    int ig = PixelUtils.clamp((int)(g+0.5));
121                                    int ib = PixelUtils.clamp((int)(b+0.5));
122                                    outPixels[outIndex] = (ia << 24) | (ir << 16) | (ig << 8) | ib;
123                    outIndex += height;
124                            }
125                    }
126            }
127    
128            /**
129             * Set the horizontal size of the blur.
130             * @param hRadius the radius of the blur in the horizontal direction
131         * @min-value 0
132         * @see #getHRadius
133             */
134            public void setHRadius(int hRadius) {
135                    this.hRadius = hRadius;
136            }
137            
138            /**
139             * Get the horizontal size of the blur.
140             * @return the radius of the blur in the horizontal direction
141         * @see #setHRadius
142             */
143            public int getHRadius() {
144                    return hRadius;
145            }
146            
147            /**
148             * Set the vertical size of the blur.
149             * @param vRadius the radius of the blur in the vertical direction
150         * @min-value 0
151         * @see #getVRadius
152             */
153            public void setVRadius(int vRadius) {
154                    this.vRadius = vRadius;
155            }
156            
157            /**
158             * Get the vertical size of the blur.
159             * @return the radius of the blur in the vertical direction
160         * @see #setVRadius
161             */
162            public int getVRadius() {
163                    return vRadius;
164            }
165            
166            /**
167             * Set the radius of the effect.
168             * @param radius the radius
169         * @min-value 0
170         * @see #getRadius
171             */
172            public void setRadius(int radius) {
173                    this.hRadius = this.vRadius = radius;
174            }
175            
176            /**
177             * Get the radius of the effect.
178             * @return the radius
179         * @see #setRadius
180             */
181            public int getRadius() {
182                    return hRadius;
183            }
184            
185            /**
186         * Set the threshold value.
187         * @param threshold the threshold value
188         * @see #getThreshold
189         */
190            public void setThreshold(int threshold) {
191                    this.threshold = threshold;
192            }
193            
194            /**
195         * Get the threshold value.
196         * @return the threshold value
197         * @see #setThreshold
198         */
199            public int getThreshold() {
200                    return threshold;
201            }
202            
203            public String toString() {
204                    return "Blur/Smart Blur...";
205            }
206            public BufferedImage filter(BufferedImage src, Struct parameters) throws PageException {BufferedImage dst=ImageUtil.createBufferedImage(src);
207                    Object o;
208                    if((o=parameters.removeEL(KeyImpl.init("HRadius")))!=null)setHRadius(ImageFilterUtil.toIntValue(o,"HRadius"));
209                    if((o=parameters.removeEL(KeyImpl.init("VRadius")))!=null)setVRadius(ImageFilterUtil.toIntValue(o,"VRadius"));
210                    if((o=parameters.removeEL(KeyImpl.init("Radius")))!=null)setRadius(ImageFilterUtil.toIntValue(o,"Radius"));
211                    if((o=parameters.removeEL(KeyImpl.init("Threshold")))!=null)setThreshold(ImageFilterUtil.toIntValue(o,"Threshold"));
212    
213                    // check for arguments not supported
214                    if(parameters.size()>0) {
215                            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 [HRadius, VRadius, Radius, Threshold]");
216                    }
217    
218                    return filter(src, dst);
219            }
220    }