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 performs a box blur on an image. The horizontal and vertical blurs can be specified separately
025 * and a number of iterations can be given which allows an approximation to Gaussian blur.
026 */
027public class BoxBlurFilter extends AbstractBufferedImageOp {
028
029        private float hRadius;
030        private float vRadius;
031        private int iterations = 1;
032        private boolean premultiplyAlpha = true;
033        
034    /**
035     * Construct a default BoxBlurFilter.
036     */
037    public BoxBlurFilter() {
038        }
039        
040    /**
041     * Construct a BoxBlurFilter.
042     * @param hRadius the horizontal radius of blur
043     * @param vRadius the vertical radius of blur
044     * @param iterations the number of time to iterate the blur
045     */
046    public BoxBlurFilter( float hRadius, float vRadius, int iterations ) {
047                this.hRadius = hRadius;
048                this.vRadius = vRadius;
049                this.iterations = iterations;
050        }
051        
052    /**
053     * Set whether to premultiply the alpha channel.
054     * @param premultiplyAlpha true to premultiply the alpha
055     * @see #getPremultiplyAlpha
056     */
057        public void setPremultiplyAlpha( boolean premultiplyAlpha ) {
058                this.premultiplyAlpha = premultiplyAlpha;
059        }
060
061    /**
062     * Get whether to premultiply the alpha channel.
063     * @return true to premultiply the alpha
064     * @see #setPremultiplyAlpha
065     */
066        public boolean getPremultiplyAlpha() {
067                return premultiplyAlpha;
068        }
069
070        public BufferedImage filter( BufferedImage src, BufferedImage dst ) {
071        int width = src.getWidth();
072        int height = src.getHeight();
073
074        if ( dst == null )
075            dst = createCompatibleDestImage( src, null );
076
077        int[] inPixels = new int[width*height];
078        int[] outPixels = new int[width*height];
079        getRGB( src, 0, 0, width, height, inPixels );
080
081        if ( premultiplyAlpha )
082                        ImageMath.premultiply( inPixels, 0, inPixels.length );
083                for (int i = 0; i < iterations; i++ ) {
084            blur( inPixels, outPixels, width, height, hRadius );
085            blur( outPixels, inPixels, height, width, vRadius );
086        }
087        blurFractional( inPixels, outPixels, width, height, hRadius );
088        blurFractional( outPixels, inPixels, height, width, vRadius );
089        if ( premultiplyAlpha )
090                        ImageMath.unpremultiply( inPixels, 0, inPixels.length );
091
092        setRGB( dst, 0, 0, width, height, inPixels );
093        return dst;
094    }
095
096    /**
097     * Blur and transpose a block of ARGB pixels.
098     * @param in the input pixels
099     * @param out the output pixels
100     * @param width the width of the pixel array
101     * @param height the height of the pixel array
102     * @param radius the radius of blur
103     */
104    public static void blur( int[] in, int[] out, int width, int height, float radius ) {
105        int widthMinus1 = width-1;
106        int r = (int)radius;
107        int tableSize = 2*r+1;
108        int divide[] = new int[256*tableSize];
109
110        for ( int i = 0; i < 256*tableSize; i++ )
111            divide[i] = i/tableSize;
112
113        int inIndex = 0;
114        
115        for ( int y = 0; y < height; y++ ) {
116            int outIndex = y;
117            int ta = 0, tr = 0, tg = 0, tb = 0;
118
119            for ( int i = -r; i <= r; i++ ) {
120                int rgb = in[inIndex + ImageMath.clamp(i, 0, width-1)];
121                ta += (rgb >> 24) & 0xff;
122                tr += (rgb >> 16) & 0xff;
123                tg += (rgb >> 8) & 0xff;
124                tb += rgb & 0xff;
125            }
126
127            for ( int x = 0; x < width; x++ ) {
128                out[ outIndex ] = (divide[ta] << 24) | (divide[tr] << 16) | (divide[tg] << 8) | divide[tb];
129
130                int i1 = x+r+1;
131                if ( i1 > widthMinus1 )
132                    i1 = widthMinus1;
133                int i2 = x-r;
134                if ( i2 < 0 )
135                    i2 = 0;
136                int rgb1 = in[inIndex+i1];
137                int rgb2 = in[inIndex+i2];
138
139                ta += ((rgb1 >> 24) & 0xff)-((rgb2 >> 24) & 0xff);
140                tr += ((rgb1 & 0xff0000)-(rgb2 & 0xff0000)) >> 16;
141                tg += ((rgb1 & 0xff00)-(rgb2 & 0xff00)) >> 8;
142                tb += (rgb1 & 0xff)-(rgb2 & 0xff);
143                outIndex += height;
144            }
145            inIndex += width;
146        }
147    }
148        
149    public static void blurFractional( int[] in, int[] out, int width, int height, float radius ) {
150        radius -= (int)radius;
151        float f = 1.0f/(1+2*radius);
152        int inIndex = 0;
153        
154        for ( int y = 0; y < height; y++ ) {
155            int outIndex = y;
156
157            out[ outIndex ] = in[0];
158            outIndex += height;
159            for ( int x = 1; x < width-1; x++ ) {
160                int i = inIndex+x;
161                int rgb1 = in[i-1];
162                int rgb2 = in[i];
163                int rgb3 = in[i+1];
164
165                int a1 = (rgb1 >> 24) & 0xff;
166                int r1 = (rgb1 >> 16) & 0xff;
167                int g1 = (rgb1 >> 8) & 0xff;
168                int b1 = rgb1 & 0xff;
169                int a2 = (rgb2 >> 24) & 0xff;
170                int r2 = (rgb2 >> 16) & 0xff;
171                int g2 = (rgb2 >> 8) & 0xff;
172                int b2 = rgb2 & 0xff;
173                int a3 = (rgb3 >> 24) & 0xff;
174                int r3 = (rgb3 >> 16) & 0xff;
175                int g3 = (rgb3 >> 8) & 0xff;
176                int b3 = rgb3 & 0xff;
177                a1 = a2 + (int)((a1 + a3) * radius);
178                r1 = r2 + (int)((r1 + r3) * radius);
179                g1 = g2 + (int)((g1 + g3) * radius);
180                b1 = b2 + (int)((b1 + b3) * radius);
181                a1 *= f;
182                r1 *= f;
183                g1 *= f;
184                b1 *= f;
185                out[ outIndex ] = (a1 << 24) | (r1 << 16) | (g1 << 8) | b1;
186                outIndex += height;
187            }
188            out[ outIndex ] = in[width-1];
189            inIndex += width;
190        }
191    }
192
193        /**
194         * Set the horizontal size of the blur.
195         * @param hRadius the radius of the blur in the horizontal direction
196     * @min-value 0
197     * @see #getHRadius
198         */
199        public void setHRadius(float hRadius) {
200                this.hRadius = hRadius;
201        }
202        
203        /**
204         * Get the horizontal size of the blur.
205         * @return the radius of the blur in the horizontal direction
206     * @see #setHRadius
207         */
208        public float getHRadius() {
209                return hRadius;
210        }
211        
212        /**
213         * Set the vertical size of the blur.
214         * @param vRadius the radius of the blur in the vertical direction
215     * @min-value 0
216     * @see #getVRadius
217         */
218        public void setVRadius(float vRadius) {
219                this.vRadius = vRadius;
220        }
221        
222        /**
223         * Get the vertical size of the blur.
224         * @return the radius of the blur in the vertical direction
225     * @see #setVRadius
226         */
227        public float getVRadius() {
228                return vRadius;
229        }
230        
231        /**
232         * Set both the horizontal and vertical sizes of the blur.
233         * @param radius the radius of the blur in both directions
234     * @min-value 0
235     * @see #getRadius
236         */
237        public void setRadius(float radius) {
238                this.hRadius = this.vRadius = radius;
239        }
240        
241        /**
242         * Get the size of the blur.
243         * @return the radius of the blur in the horizontal direction
244     * @see #setRadius
245         */
246        public float getRadius() {
247                return hRadius;
248        }
249        
250        /**
251         * Set the number of iterations the blur is performed.
252         * @param iterations the number of iterations
253     * @min-value 0
254     * @see #getIterations
255         */
256        public void setIterations(int iterations) {
257                this.iterations = iterations;
258        }
259        
260        /**
261         * Get the number of iterations the blur is performed.
262         * @return the number of iterations
263     * @see #setIterations
264         */
265        public int getIterations() {
266                return iterations;
267        }
268        
269        public String toString() {
270                return "Blur/Box Blur...";
271        }
272}