001/*
002Copyright 2006 Jerry Huxtable
003
004Licensed under the Apache License, Version 2.0 (the "License");
005you may not use this file except in compliance with the License.
006You may obtain a copy of the License at
007
008   http://www.apache.org/licenses/LICENSE-2.0
009
010Unless required by applicable law or agreed to in writing, software
011distributed under the License is distributed on an "AS IS" BASIS,
012WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013See the License for the specific language governing permissions and
014limitations under the License.
015*/
016
017package com.jhlabs.image;
018
019import java.awt.*;
020import java.awt.image.*;
021import java.awt.geom.*;
022
023/**
024 * A filter which applies a convolution kernel to an image.
025 * @author Jerry Huxtable
026 */
027public class ConvolveFilter extends AbstractBufferedImageOp {
028        
029    /**
030     * Treat pixels off the edge as zero.
031     */
032        public static int ZERO_EDGES = 0;
033
034    /**
035     * Clamp pixels off the edge to the nearest edge.
036     */
037        public static int CLAMP_EDGES = 1;
038
039    /**
040     * Wrap pixels off the edge to the opposite edge.
041     */
042        public static int WRAP_EDGES = 2;
043
044    /**
045     * The convolution kernel.
046     */
047        protected Kernel kernel = null;
048
049    /**
050     * Whether to convolve alpha.
051     */
052        protected boolean alpha = true;
053
054    /**
055     * Whether to promultiply the alpha before convolving.
056     */
057        protected boolean premultiplyAlpha = true;
058
059    /**
060     * What do do at the image edges.
061     */
062        private int edgeAction = CLAMP_EDGES;
063
064        /**
065         * Construct a filter with a null kernel. This is only useful if you're going to change the kernel later on.
066         */
067        public ConvolveFilter() {
068                this(new float[9]);
069        }
070
071        /**
072         * Construct a filter with the given 3x3 kernel.
073         * @param matrix an array of 9 floats containing the kernel
074         */
075        public ConvolveFilter(float[] matrix) {
076                this(new Kernel(3, 3, matrix));
077        }
078        
079        /**
080         * Construct a filter with the given kernel.
081         * @param rows  the number of rows in the kernel
082         * @param cols  the number of columns in the kernel
083         * @param matrix        an array of rows*cols floats containing the kernel
084         */
085        public ConvolveFilter(int rows, int cols, float[] matrix) {
086                this(new Kernel(cols, rows, matrix));
087        }
088        
089        /**
090         * Construct a filter with the given 3x3 kernel.
091         * @param kernel the convolution kernel
092         */
093        public ConvolveFilter(Kernel kernel) {
094                this.kernel = kernel;   
095        }
096
097    /**
098     * Set the convolution kernel.
099     * @param kernel the kernel
100     * @see #getKernel
101     */
102        public void setKernel(Kernel kernel) {
103                this.kernel = kernel;
104        }
105
106    /**
107     * Get the convolution kernel.
108     * @return the kernel
109     * @see #setKernel
110     */
111        public Kernel getKernel() {
112                return kernel;
113        }
114
115    /**
116     * Set the action to perfomr for pixels off the image edges.
117     * @param edgeAction the action
118     * @see #getEdgeAction
119     */
120        public void setEdgeAction(int edgeAction) {
121                this.edgeAction = edgeAction;
122        }
123
124    /**
125     * Get the action to perfomr for pixels off the image edges.
126     * @return the action
127     * @see #setEdgeAction
128     */
129        public int getEdgeAction() {
130                return edgeAction;
131        }
132
133    /**
134     * Set whether to convolve the alpha channel.
135     * @param useAlpha true to convolve the alpha
136     * @see #getUseAlpha
137     */
138        public void setUseAlpha( boolean useAlpha ) {
139                this.alpha = useAlpha;
140        }
141
142    /**
143     * Get whether to convolve the alpha channel.
144     * @return true to convolve the alpha
145     * @see #setUseAlpha
146     */
147        public boolean getUseAlpha() {
148                return alpha;
149        }
150
151    /**
152     * Set whether to premultiply the alpha channel.
153     * @param premultiplyAlpha true to premultiply the alpha
154     * @see #getPremultiplyAlpha
155     */
156        public void setPremultiplyAlpha( boolean premultiplyAlpha ) {
157                this.premultiplyAlpha = premultiplyAlpha;
158        }
159
160    /**
161     * Get whether to premultiply the alpha channel.
162     * @return true to premultiply the alpha
163     * @see #setPremultiplyAlpha
164     */
165        public boolean getPremultiplyAlpha() {
166                return premultiplyAlpha;
167        }
168
169    public BufferedImage filter( BufferedImage src, BufferedImage dst ) {
170        int width = src.getWidth();
171        int height = src.getHeight();
172
173        if ( dst == null )
174            dst = createCompatibleDestImage( src, null );
175
176        int[] inPixels = new int[width*height];
177        int[] outPixels = new int[width*height];
178        getRGB( src, 0, 0, width, height, inPixels );
179
180        if ( premultiplyAlpha )
181                        ImageMath.premultiply( inPixels, 0, inPixels.length );
182                convolve(kernel, inPixels, outPixels, width, height, alpha, edgeAction);
183        if ( premultiplyAlpha )
184                        ImageMath.unpremultiply( outPixels, 0, outPixels.length );
185
186        setRGB( dst, 0, 0, width, height, outPixels );
187        return dst;
188    }
189
190    public BufferedImage createCompatibleDestImage(BufferedImage src, ColorModel dstCM) {
191        if ( dstCM == null )
192            dstCM = src.getColorModel();
193        return new BufferedImage(dstCM, dstCM.createCompatibleWritableRaster(src.getWidth(), src.getHeight()), dstCM.isAlphaPremultiplied(), null);
194    }
195    
196    public Rectangle2D getBounds2D( BufferedImage src ) {
197        return new Rectangle(0, 0, src.getWidth(), src.getHeight());
198    }
199    
200    public Point2D getPoint2D( Point2D srcPt, Point2D dstPt ) {
201        if ( dstPt == null )
202            dstPt = new Point2D.Double();
203        dstPt.setLocation( srcPt.getX(), srcPt.getY() );
204        return dstPt;
205    }
206
207    public RenderingHints getRenderingHints() {
208        return null;
209    }
210
211    /**
212     * Convolve a block of pixels.
213     * @param kernel the kernel
214     * @param inPixels the input pixels
215     * @param outPixels the output pixels
216     * @param width the width
217     * @param height the height
218     * @param edgeAction what to do at the edges
219     */
220        public static void convolve(Kernel kernel, int[] inPixels, int[] outPixels, int width, int height, int edgeAction) {
221                convolve(kernel, inPixels, outPixels, width, height, true, edgeAction);
222        }
223        
224    /**
225     * Convolve a block of pixels.
226     * @param kernel the kernel
227     * @param inPixels the input pixels
228     * @param outPixels the output pixels
229     * @param width the width
230     * @param height the height
231     * @param alpha include alpha channel
232     * @param edgeAction what to do at the edges
233     */
234        public static void convolve(Kernel kernel, int[] inPixels, int[] outPixels, int width, int height, boolean alpha, int edgeAction) {
235                if (kernel.getHeight() == 1)
236                        convolveH(kernel, inPixels, outPixels, width, height, alpha, edgeAction);
237                else if (kernel.getWidth() == 1)
238                        convolveV(kernel, inPixels, outPixels, width, height, alpha, edgeAction);
239                else
240                        convolveHV(kernel, inPixels, outPixels, width, height, alpha, edgeAction);
241        }
242        
243        /**
244         * Convolve with a 2D kernel.
245     * @param kernel the kernel
246     * @param inPixels the input pixels
247     * @param outPixels the output pixels
248     * @param width the width
249     * @param height the height
250     * @param alpha include alpha channel
251     * @param edgeAction what to do at the edges
252         */
253        public static void convolveHV(Kernel kernel, int[] inPixels, int[] outPixels, int width, int height, boolean alpha, int edgeAction) {
254                int index = 0;
255                float[] matrix = kernel.getKernelData( null );
256                int rows = kernel.getHeight();
257                int cols = kernel.getWidth();
258                int rows2 = rows/2;
259                int cols2 = cols/2;
260
261                for (int y = 0; y < height; y++) {
262                        for (int x = 0; x < width; x++) {
263                                float r = 0, g = 0, b = 0, a = 0;
264
265                                for (int row = -rows2; row <= rows2; row++) {
266                                        int iy = y+row;
267                                        int ioffset;
268                                        if (0 <= iy && iy < height)
269                                                ioffset = iy*width;
270                                        else if ( edgeAction == CLAMP_EDGES )
271                                                ioffset = y*width;
272                                        else if ( edgeAction == WRAP_EDGES )
273                                                ioffset = ((iy+height) % height) * width;
274                                        else
275                                                continue;
276                                        int moffset = cols*(row+rows2)+cols2;
277                                        for (int col = -cols2; col <= cols2; col++) {
278                                                float f = matrix[moffset+col];
279
280                                                if (f != 0) {
281                                                        int ix = x+col;
282                                                        if (!(0 <= ix && ix < width)) {
283                                                                if ( edgeAction == CLAMP_EDGES )
284                                                                        ix = x;
285                                                                else if ( edgeAction == WRAP_EDGES )
286                                                                        ix = (x+width) % width;
287                                                                else
288                                                                        continue;
289                                                        }
290                                                        int rgb = inPixels[ioffset+ix];
291                                                        a += f * ((rgb >> 24) & 0xff);
292                                                        r += f * ((rgb >> 16) & 0xff);
293                                                        g += f * ((rgb >> 8) & 0xff);
294                                                        b += f * (rgb & 0xff);
295                                                }
296                                        }
297                                }
298                                int ia = alpha ? PixelUtils.clamp((int)(a+0.5)) : 0xff;
299                                int ir = PixelUtils.clamp((int)(r+0.5));
300                                int ig = PixelUtils.clamp((int)(g+0.5));
301                                int ib = PixelUtils.clamp((int)(b+0.5));
302                                outPixels[index++] = (ia << 24) | (ir << 16) | (ig << 8) | ib;
303                        }
304                }
305        }
306
307        /**
308         * Convolve with a kernel consisting of one row.
309     * @param kernel the kernel
310     * @param inPixels the input pixels
311     * @param outPixels the output pixels
312     * @param width the width
313     * @param height the height
314     * @param alpha include alpha channel
315     * @param edgeAction what to do at the edges
316         */
317        public static void convolveH(Kernel kernel, int[] inPixels, int[] outPixels, int width, int height, boolean alpha, int edgeAction) {
318                int index = 0;
319                float[] matrix = kernel.getKernelData( null );
320                int cols = kernel.getWidth();
321                int cols2 = cols/2;
322
323                for (int y = 0; y < height; y++) {
324                        int ioffset = y*width;
325                        for (int x = 0; x < width; x++) {
326                                float r = 0, g = 0, b = 0, a = 0;
327                                int moffset = cols2;
328                                for (int col = -cols2; col <= cols2; col++) {
329                                        float f = matrix[moffset+col];
330
331                                        if (f != 0) {
332                                                int ix = x+col;
333                                                if ( ix < 0 ) {
334                                                        if ( edgeAction == CLAMP_EDGES )
335                                                                ix = 0;
336                                                        else if ( edgeAction == WRAP_EDGES )
337                                                                ix = (x+width) % width;
338                                                } else if ( ix >= width) {
339                                                        if ( edgeAction == CLAMP_EDGES )
340                                                                ix = width-1;
341                                                        else if ( edgeAction == WRAP_EDGES )
342                                                                ix = (x+width) % width;
343                                                }
344                                                int rgb = inPixels[ioffset+ix];
345                                                a += f * ((rgb >> 24) & 0xff);
346                                                r += f * ((rgb >> 16) & 0xff);
347                                                g += f * ((rgb >> 8) & 0xff);
348                                                b += f * (rgb & 0xff);
349                                        }
350                                }
351                                int ia = alpha ? PixelUtils.clamp((int)(a+0.5)) : 0xff;
352                                int ir = PixelUtils.clamp((int)(r+0.5));
353                                int ig = PixelUtils.clamp((int)(g+0.5));
354                                int ib = PixelUtils.clamp((int)(b+0.5));
355                                outPixels[index++] = (ia << 24) | (ir << 16) | (ig << 8) | ib;
356                        }
357                }
358        }
359
360        /**
361         * Convolve with a kernel consisting of one column.
362     * @param kernel the kernel
363     * @param inPixels the input pixels
364     * @param outPixels the output pixels
365     * @param width the width
366     * @param height the height
367     * @param alpha include alpha channel
368     * @param edgeAction what to do at the edges
369         */
370        public static void convolveV(Kernel kernel, int[] inPixels, int[] outPixels, int width, int height, boolean alpha, int edgeAction) {
371                int index = 0;
372                float[] matrix = kernel.getKernelData( null );
373                int rows = kernel.getHeight();
374                int rows2 = rows/2;
375
376                for (int y = 0; y < height; y++) {
377                        for (int x = 0; x < width; x++) {
378                                float r = 0, g = 0, b = 0, a = 0;
379
380                                for (int row = -rows2; row <= rows2; row++) {
381                                        int iy = y+row;
382                                        int ioffset;
383                                        if ( iy < 0 ) {
384                                                if ( edgeAction == CLAMP_EDGES )
385                                                        ioffset = 0;
386                                                else if ( edgeAction == WRAP_EDGES )
387                                                        ioffset = ((y+height) % height)*width;
388                                                else
389                                                        ioffset = iy*width;
390                                        } else if ( iy >= height) {
391                                                if ( edgeAction == CLAMP_EDGES )
392                                                        ioffset = (height-1)*width;
393                                                else if ( edgeAction == WRAP_EDGES )
394                                                        ioffset = ((y+height) % height)*width;
395                                                else
396                                                        ioffset = iy*width;
397                                        } else
398                                                ioffset = iy*width;
399
400                                        float f = matrix[row+rows2];
401
402                                        if (f != 0) {
403                                                int rgb = inPixels[ioffset+x];
404                                                a += f * ((rgb >> 24) & 0xff);
405                                                r += f * ((rgb >> 16) & 0xff);
406                                                g += f * ((rgb >> 8) & 0xff);
407                                                b += f * (rgb & 0xff);
408                                        }
409                                }
410                                int ia = alpha ? PixelUtils.clamp((int)(a+0.5)) : 0xff;
411                                int ir = PixelUtils.clamp((int)(r+0.5));
412                                int ig = PixelUtils.clamp((int)(g+0.5));
413                                int ib = PixelUtils.clamp((int)(b+0.5));
414                                outPixels[index++] = (ia << 24) | (ir << 16) | (ig << 8) | ib;
415                        }
416                }
417        }
418
419        public String toString() {
420                return "Blur/Convolve...";
421        }
422}