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 "smart blur". i.e. a blur which blurs smotth parts of the image while preserving edges.
025 */
026public class SmartBlurFilter extends AbstractBufferedImageOp {
027
028        private int hRadius = 5;
029        private int vRadius = 5;
030        private int threshold = 10;
031        
032    public BufferedImage filter( BufferedImage src, BufferedImage dst ) {
033        int width = src.getWidth();
034        int height = src.getHeight();
035
036        if ( dst == null )
037            dst = createCompatibleDestImage( src, null );
038
039        int[] inPixels = new int[width*height];
040        int[] outPixels = new int[width*height];
041        getRGB( src, 0, 0, width, height, inPixels );
042
043                Kernel kernel = GaussianFilter.makeKernel(hRadius);
044                thresholdBlur( kernel, inPixels, outPixels, width, height, true );
045                thresholdBlur( kernel, outPixels, inPixels, height, width, true );
046
047        setRGB( dst, 0, 0, width, height, inPixels );
048        return dst;
049    }
050
051        /**
052         * Convolve with a kernel consisting of one row
053         */
054        private void thresholdBlur(Kernel kernel, int[] inPixels, int[] outPixels, int width, int height, boolean alpha) {
055                int index = 0;
056                float[] matrix = kernel.getKernelData( null );
057                int cols = kernel.getWidth();
058                int cols2 = cols/2;
059
060                for (int y = 0; y < height; y++) {
061                        int ioffset = y*width;
062            int outIndex = y;
063                        for (int x = 0; x < width; x++) {
064                                float r = 0, g = 0, b = 0, a = 0;
065                                int moffset = cols2;
066
067                int rgb1 = inPixels[ioffset+x];
068                int a1 = (rgb1 >> 24) & 0xff;
069                int r1 = (rgb1 >> 16) & 0xff;
070                int g1 = (rgb1 >> 8) & 0xff;
071                int b1 = rgb1 & 0xff;
072                                float af = 0, rf = 0, gf = 0, bf = 0;
073                for (int col = -cols2; col <= cols2; col++) {
074                                        float f = matrix[moffset+col];
075
076                                        if (f != 0) {
077                                                int ix = x+col;
078                                                if (!(0 <= ix && ix < width))
079                                                        ix = x;
080                                                int rgb2 = inPixels[ioffset+ix];
081                        int a2 = (rgb2 >> 24) & 0xff;
082                        int r2 = (rgb2 >> 16) & 0xff;
083                        int g2 = (rgb2 >> 8) & 0xff;
084                        int b2 = rgb2 & 0xff;
085
086                                                int d;
087                        d = a1-a2;
088                        if ( d >= -threshold && d <= threshold ) {
089                            a += f * a2;
090                            af += f;
091                        }
092                        d = r1-r2;
093                        if ( d >= -threshold && d <= threshold ) {
094                            r += f * r2;
095                            rf += f;
096                        }
097                        d = g1-g2;
098                        if ( d >= -threshold && d <= threshold ) {
099                            g += f * g2;
100                            gf += f;
101                        }
102                        d = b1-b2;
103                        if ( d >= -threshold && d <= threshold ) {
104                            b += f * b2;
105                            bf += f;
106                        }
107                                        }
108                                }
109                a = af == 0 ? a1 : a/af;
110                r = rf == 0 ? r1 : r/rf;
111                g = gf == 0 ? g1 : g/gf;
112                b = bf == 0 ? b1 : b/bf;
113                                int ia = alpha ? PixelUtils.clamp((int)(a+0.5)) : 0xff;
114                                int ir = PixelUtils.clamp((int)(r+0.5));
115                                int ig = PixelUtils.clamp((int)(g+0.5));
116                                int ib = PixelUtils.clamp((int)(b+0.5));
117                                outPixels[outIndex] = (ia << 24) | (ir << 16) | (ig << 8) | ib;
118                outIndex += height;
119                        }
120                }
121        }
122
123        /**
124         * Set the horizontal size of the blur.
125         * @param hRadius the radius of the blur in the horizontal direction
126     * @min-value 0
127     * @see #getHRadius
128         */
129        public void setHRadius(int hRadius) {
130                this.hRadius = hRadius;
131        }
132        
133        /**
134         * Get the horizontal size of the blur.
135         * @return the radius of the blur in the horizontal direction
136     * @see #setHRadius
137         */
138        public int getHRadius() {
139                return hRadius;
140        }
141        
142        /**
143         * Set the vertical size of the blur.
144         * @param vRadius the radius of the blur in the vertical direction
145     * @min-value 0
146     * @see #getVRadius
147         */
148        public void setVRadius(int vRadius) {
149                this.vRadius = vRadius;
150        }
151        
152        /**
153         * Get the vertical size of the blur.
154         * @return the radius of the blur in the vertical direction
155     * @see #setVRadius
156         */
157        public int getVRadius() {
158                return vRadius;
159        }
160        
161        /**
162         * Set the radius of the effect.
163         * @param radius the radius
164     * @min-value 0
165     * @see #getRadius
166         */
167        public void setRadius(int radius) {
168                this.hRadius = this.vRadius = radius;
169        }
170        
171        /**
172         * Get the radius of the effect.
173         * @return the radius
174     * @see #setRadius
175         */
176        public int getRadius() {
177                return hRadius;
178        }
179        
180        /**
181     * Set the threshold value.
182     * @param threshold the threshold value
183     * @see #getThreshold
184     */
185        public void setThreshold(int threshold) {
186                this.threshold = threshold;
187        }
188        
189        /**
190     * Get the threshold value.
191     * @return the threshold value
192     * @see #setThreshold
193     */
194        public int getThreshold() {
195                return threshold;
196        }
197        
198        public String toString() {
199                return "Blur/Smart Blur...";
200        }
201}