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