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    import java.awt.image.Kernel;
024    
025    import railo.runtime.engine.ThreadLocalPageContext;
026    import railo.runtime.exp.ExpressionException;
027    import railo.runtime.exp.FunctionException;
028    import railo.runtime.exp.PageException;
029    import railo.runtime.img.ImageUtil;
030    import railo.runtime.type.KeyImpl;
031    import railo.runtime.type.Struct;
032    import railo.runtime.type.util.CollectionUtil;
033    
034    public abstract class ConvolveFilter extends AbstractBufferedImageOp  implements DynFiltering {
035            
036        /**
037         * Treat pixels off the edge as zero.
038         */
039            public final static int ZERO_EDGES = 0;
040    
041        /**
042         * Clamp pixels off the edge to the nearest edge.
043         */
044            public final static int CLAMP_EDGES = 1;
045    
046        /**
047         * Wrap pixels off the edge to the opposite edge.
048         */
049            public final static int WRAP_EDGES = 2;
050    
051        /**
052         * The convolution kernel.
053         */
054            protected Kernel kernel = null;
055    
056        /**
057         * Whether to convolve alpha.
058         */
059            protected boolean alpha = true;
060    
061        /**
062         * Whether to promultiply the alpha before convolving.
063         */
064            protected boolean premultiplyAlpha = true;
065    
066        /**
067         * What do do at the image edges.
068         */
069            private int edgeAction = CLAMP_EDGES;
070    
071            /**
072             * Construct a filter with a null kernel. This is only useful if you're going to change the kernel later on.
073             */
074            public ConvolveFilter() {
075                    this(new float[9]);
076            }
077    
078            /**
079             * Construct a filter with the given 3x3 kernel.
080             * @param matrix an array of 9 floats containing the kernel
081             */
082            public ConvolveFilter(float[] matrix) {
083                    this(new Kernel(3, 3, matrix));
084            }
085            
086            /**
087             * Construct a filter with the given kernel.
088             * @param rows  the number of rows in the kernel
089             * @param cols  the number of columns in the kernel
090             * @param matrix        an array of rows*cols floats containing the kernel
091             */
092            public ConvolveFilter(int rows, int cols, float[] matrix) {
093                    this(new Kernel(cols, rows, matrix));
094            }
095            
096            /**
097             * Construct a filter with the given 3x3 kernel.
098             * @param kernel the convolution kernel
099             */
100            public ConvolveFilter(Kernel kernel) {
101                    this.kernel = kernel;   
102            }
103    
104        /**
105         * Set the convolution kernel.
106         * @param kernel the kernel
107         * @see #getKernel
108         */
109            public void setKernel(Kernel kernel) {
110                    this.kernel = kernel;
111            }
112    
113        /**
114         * Get the convolution kernel.
115         * @return the kernel
116         * @see #setKernel
117         */
118            public Kernel getKernel() {
119                    return kernel;
120            }
121    
122        /**
123         * Set the action to perfomr for pixels off the image edges.
124         * valid values are:
125         * - clamp (default): Clamp pixels off the edge to the nearest edge.
126         * - wrap: Wrap pixels off the edge to the opposite edge.
127         * - zero: Treat pixels off the edge as zero
128         * 
129         * @param edgeAction the action
130         * @throws ExpressionException 
131         */
132            public void setEdgeAction(String edgeAction) throws ExpressionException {
133                    String str=edgeAction.trim().toUpperCase();
134                    if("ZERO".equals(str)) this.edgeAction = ZERO_EDGES;
135                    else if("CLAMP".equals(str)) this.edgeAction = CLAMP_EDGES;
136                    else if("WRAP".equals(str)) this.edgeAction = WRAP_EDGES;
137                    else 
138                            throw new ExpressionException("invalid value ["+edgeAction+"] for edgeAction, valid values are [clamp,wrap,zero]");
139            }
140    
141        /**
142         * Get the action to perfomr for pixels off the image edges.
143         * @return the action
144         * @see #setEdgeAction
145         */
146            public int getEdgeAction() {
147                    return edgeAction;
148            }
149    
150        /**
151         * Set whether to convolve the alpha channel.
152         * @param useAlpha true to convolve the alpha
153         * @see #getUseAlpha
154         */
155            public void setUseAlpha( boolean useAlpha ) {
156                    this.alpha = useAlpha;
157            }
158    
159        /**
160         * Get whether to convolve the alpha channel.
161         * @return true to convolve the alpha
162         * @see #setUseAlpha
163         */
164            public boolean getUseAlpha() {
165                    return alpha;
166            }
167    
168        /**
169         * Set whether to premultiply the alpha channel.
170         * @param premultiplyAlpha true to premultiply the alpha
171         * @see #getPremultiplyAlpha
172         */
173            public void setPremultiplyAlpha( boolean premultiplyAlpha ) {
174                    this.premultiplyAlpha = premultiplyAlpha;
175            }
176    
177        /**
178         * Get whether to premultiply the alpha channel.
179         * @return true to premultiply the alpha
180         * @see #setPremultiplyAlpha
181         */
182            public boolean getPremultiplyAlpha() {
183                    return premultiplyAlpha;
184            }
185    
186        public BufferedImage filter( BufferedImage src, BufferedImage dst ) {
187            int width = src.getWidth();
188            int height = src.getHeight();
189    
190            if ( dst == null )
191                dst = createCompatibleDestImage( src, null );
192    
193            int[] inPixels = new int[width*height];
194            int[] outPixels = new int[width*height];
195            getRGB( src, 0, 0, width, height, inPixels );
196    
197            if ( premultiplyAlpha )
198                            ImageMath.premultiply( inPixels, 0, inPixels.length );
199                    convolve(kernel, inPixels, outPixels, width, height, alpha, edgeAction);
200            if ( premultiplyAlpha )
201                            ImageMath.unpremultiply( outPixels, 0, outPixels.length );
202    
203            setRGB( dst, 0, 0, width, height, outPixels );
204            return dst;
205        }
206    
207        public BufferedImage createCompatibleDestImage(BufferedImage src, ColorModel dstCM) {
208            if ( dstCM == null )
209                dstCM = src.getColorModel();
210            return new BufferedImage(dstCM, dstCM.createCompatibleWritableRaster(src.getWidth(), src.getHeight()), dstCM.isAlphaPremultiplied(), null);
211        }
212        
213        public Rectangle2D getBounds2D( BufferedImage src ) {
214            return new Rectangle(0, 0, src.getWidth(), src.getHeight());
215        }
216        
217        public Point2D getPoint2D( Point2D srcPt, Point2D dstPt ) {
218            if ( dstPt == null )
219                dstPt = new Point2D.Double();
220            dstPt.setLocation( srcPt.getX(), srcPt.getY() );
221            return dstPt;
222        }
223    
224        public RenderingHints getRenderingHints() {
225            return null;
226        }
227    
228        /**
229         * Convolve a block of pixels.
230         * @param kernel the kernel
231         * @param inPixels the input pixels
232         * @param outPixels the output pixels
233         * @param width the width
234         * @param height the height
235         * @param edgeAction what to do at the edges
236         */
237            public static void convolve(Kernel kernel, int[] inPixels, int[] outPixels, int width, int height, int edgeAction) {
238                    convolve(kernel, inPixels, outPixels, width, height, true, edgeAction);
239            }
240            
241        /**
242         * Convolve a block of pixels.
243         * @param kernel the kernel
244         * @param inPixels the input pixels
245         * @param outPixels the output pixels
246         * @param width the width
247         * @param height the height
248         * @param alpha include alpha channel
249         * @param edgeAction what to do at the edges
250         */
251            public static void convolve(Kernel kernel, int[] inPixels, int[] outPixels, int width, int height, boolean alpha, int edgeAction) {
252                    if (kernel.getHeight() == 1)
253                            convolveH(kernel, inPixels, outPixels, width, height, alpha, edgeAction);
254                    else if (kernel.getWidth() == 1)
255                            convolveV(kernel, inPixels, outPixels, width, height, alpha, edgeAction);
256                    else
257                            convolveHV(kernel, inPixels, outPixels, width, height, alpha, edgeAction);
258            }
259            
260            /**
261             * Convolve with a 2D kernel.
262         * @param kernel the kernel
263         * @param inPixels the input pixels
264         * @param outPixels the output pixels
265         * @param width the width
266         * @param height the height
267         * @param alpha include alpha channel
268         * @param edgeAction what to do at the edges
269             */
270            public static void convolveHV(Kernel kernel, int[] inPixels, int[] outPixels, int width, int height, boolean alpha, int edgeAction) {
271                    int index = 0;
272                    float[] matrix = kernel.getKernelData( null );
273                    int rows = kernel.getHeight();
274                    int cols = kernel.getWidth();
275                    int rows2 = rows/2;
276                    int cols2 = cols/2;
277    
278                    for (int y = 0; y < height; y++) {
279                            for (int x = 0; x < width; x++) {
280                                    float r = 0, g = 0, b = 0, a = 0;
281    
282                                    for (int row = -rows2; row <= rows2; row++) {
283                                            int iy = y+row;
284                                            int ioffset;
285                                            if (0 <= iy && iy < height)
286                                                    ioffset = iy*width;
287                                            else if ( edgeAction == CLAMP_EDGES )
288                                                    ioffset = y*width;
289                                            else if ( edgeAction == WRAP_EDGES )
290                                                    ioffset = ((iy+height) % height) * width;
291                                            else
292                                                    continue;
293                                            int moffset = cols*(row+rows2)+cols2;
294                                            for (int col = -cols2; col <= cols2; col++) {
295                                                    float f = matrix[moffset+col];
296    
297                                                    if (f != 0) {
298                                                            int ix = x+col;
299                                                            if (!(0 <= ix && ix < width)) {
300                                                                    if ( edgeAction == CLAMP_EDGES )
301                                                                            ix = x;
302                                                                    else if ( edgeAction == WRAP_EDGES )
303                                                                            ix = (x+width) % width;
304                                                                    else
305                                                                            continue;
306                                                            }
307                                                            int rgb = inPixels[ioffset+ix];
308                                                            a += f * ((rgb >> 24) & 0xff);
309                                                            r += f * ((rgb >> 16) & 0xff);
310                                                            g += f * ((rgb >> 8) & 0xff);
311                                                            b += f * (rgb & 0xff);
312                                                    }
313                                            }
314                                    }
315                                    int ia = alpha ? PixelUtils.clamp((int)(a+0.5)) : 0xff;
316                                    int ir = PixelUtils.clamp((int)(r+0.5));
317                                    int ig = PixelUtils.clamp((int)(g+0.5));
318                                    int ib = PixelUtils.clamp((int)(b+0.5));
319                                    outPixels[index++] = (ia << 24) | (ir << 16) | (ig << 8) | ib;
320                            }
321                    }
322            }
323    
324            /**
325             * Convolve with a kernel consisting of one row.
326         * @param kernel the kernel
327         * @param inPixels the input pixels
328         * @param outPixels the output pixels
329         * @param width the width
330         * @param height the height
331         * @param alpha include alpha channel
332         * @param edgeAction what to do at the edges
333             */
334            public static void convolveH(Kernel kernel, int[] inPixels, int[] outPixels, int width, int height, boolean alpha, int edgeAction) {
335                    int index = 0;
336                    float[] matrix = kernel.getKernelData( null );
337                    int cols = kernel.getWidth();
338                    int cols2 = cols/2;
339    
340                    for (int y = 0; y < height; y++) {
341                            int ioffset = y*width;
342                            for (int x = 0; x < width; x++) {
343                                    float r = 0, g = 0, b = 0, a = 0;
344                                    int moffset = cols2;
345                                    for (int col = -cols2; col <= cols2; col++) {
346                                            float f = matrix[moffset+col];
347    
348                                            if (f != 0) {
349                                                    int ix = x+col;
350                                                    if ( ix < 0 ) {
351                                                            if ( edgeAction == CLAMP_EDGES )
352                                                                    ix = 0;
353                                                            else if ( edgeAction == WRAP_EDGES )
354                                                                    ix = (x+width) % width;
355                                                    } else if ( ix >= width) {
356                                                            if ( edgeAction == CLAMP_EDGES )
357                                                                    ix = width-1;
358                                                            else if ( edgeAction == WRAP_EDGES )
359                                                                    ix = (x+width) % width;
360                                                    }
361                                                    int rgb = inPixels[ioffset+ix];
362                                                    a += f * ((rgb >> 24) & 0xff);
363                                                    r += f * ((rgb >> 16) & 0xff);
364                                                    g += f * ((rgb >> 8) & 0xff);
365                                                    b += f * (rgb & 0xff);
366                                            }
367                                    }
368                                    int ia = alpha ? PixelUtils.clamp((int)(a+0.5)) : 0xff;
369                                    int ir = PixelUtils.clamp((int)(r+0.5));
370                                    int ig = PixelUtils.clamp((int)(g+0.5));
371                                    int ib = PixelUtils.clamp((int)(b+0.5));
372                                    outPixels[index++] = (ia << 24) | (ir << 16) | (ig << 8) | ib;
373                            }
374                    }
375            }
376    
377            /**
378             * Convolve with a kernel consisting of one column.
379         * @param kernel the kernel
380         * @param inPixels the input pixels
381         * @param outPixels the output pixels
382         * @param width the width
383         * @param height the height
384         * @param alpha include alpha channel
385         * @param edgeAction what to do at the edges
386             */
387            public static void convolveV(Kernel kernel, int[] inPixels, int[] outPixels, int width, int height, boolean alpha, int edgeAction) {
388                    int index = 0;
389                    float[] matrix = kernel.getKernelData( null );
390                    int rows = kernel.getHeight();
391                    int rows2 = rows/2;
392    
393                    for (int y = 0; y < height; y++) {
394                            for (int x = 0; x < width; x++) {
395                                    float r = 0, g = 0, b = 0, a = 0;
396    
397                                    for (int row = -rows2; row <= rows2; row++) {
398                                            int iy = y+row;
399                                            int ioffset;
400                                            if ( iy < 0 ) {
401                                                    if ( edgeAction == CLAMP_EDGES )
402                                                            ioffset = 0;
403                                                    else if ( edgeAction == WRAP_EDGES )
404                                                            ioffset = ((y+height) % height)*width;
405                                                    else
406                                                            ioffset = iy*width;
407                                            } else if ( iy >= height) {
408                                                    if ( edgeAction == CLAMP_EDGES )
409                                                            ioffset = (height-1)*width;
410                                                    else if ( edgeAction == WRAP_EDGES )
411                                                            ioffset = ((y+height) % height)*width;
412                                                    else
413                                                            ioffset = iy*width;
414                                            } else
415                                                    ioffset = iy*width;
416    
417                                            float f = matrix[row+rows2];
418    
419                                            if (f != 0) {
420                                                    int rgb = inPixels[ioffset+x];
421                                                    a += f * ((rgb >> 24) & 0xff);
422                                                    r += f * ((rgb >> 16) & 0xff);
423                                                    g += f * ((rgb >> 8) & 0xff);
424                                                    b += f * (rgb & 0xff);
425                                            }
426                                    }
427                                    int ia = alpha ? PixelUtils.clamp((int)(a+0.5)) : 0xff;
428                                    int ir = PixelUtils.clamp((int)(r+0.5));
429                                    int ig = PixelUtils.clamp((int)(g+0.5));
430                                    int ib = PixelUtils.clamp((int)(b+0.5));
431                                    outPixels[index++] = (ia << 24) | (ir << 16) | (ig << 8) | ib;
432                            }
433                    }
434            }
435    
436            public String toString() {
437                    return "Blur/Convolve...";
438            }
439            public BufferedImage filter(BufferedImage src, Struct parameters) throws PageException {BufferedImage dst=ImageUtil.createBufferedImage(src);
440                    Object o;
441                    if((o=parameters.removeEL(KeyImpl.init("EdgeAction")))!=null)setEdgeAction(ImageFilterUtil.toString(o,"EdgeAction"));
442                    if((o=parameters.removeEL(KeyImpl.init("UseAlpha")))!=null)setUseAlpha(ImageFilterUtil.toBooleanValue(o,"UseAlpha"));
443                    if((o=parameters.removeEL(KeyImpl.init("PremultiplyAlpha")))!=null)setPremultiplyAlpha(ImageFilterUtil.toBooleanValue(o,"PremultiplyAlpha"));
444    
445                    // check for arguments not supported
446                    if(parameters.size()>0) {
447                            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 [Kernel, EdgeAction, UseAlpha, PremultiplyAlpha]");
448                    }
449    
450                    return filter(src, dst);
451            }
452    }