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