001/*
002 * $Id: FastBlurFilter.java 4082 2011-11-15 18:39:43Z kschaefe $
003 *
004 * Dual-licensed under LGPL (Sun and Romain Guy) and BSD (Romain Guy).
005 *
006 * Copyright 2006 Sun Microsystems, Inc., 4150 Network Circle,
007 * Santa Clara, California 95054, U.S.A. All rights reserved.
008 *
009 * Copyright (c) 2006 Romain Guy <romain.guy@mac.com>
010 * All rights reserved.
011 *
012 * Redistribution and use in source and binary forms, with or without
013 * modification, are permitted provided that the following conditions
014 * are met:
015 * 1. Redistributions of source code must retain the above copyright
016 *    notice, this list of conditions and the following disclaimer.
017 * 2. Redistributions in binary form must reproduce the above copyright
018 *    notice, this list of conditions and the following disclaimer in the
019 *    documentation and/or other materials provided with the distribution.
020 * 3. The name of the author may not be used to endorse or promote products
021 *    derived from this software without specific prior written permission.
022 *
023 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
024 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
025 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
026 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
027 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
028 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
029 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
030 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
031 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
032 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
033 */
034
035package org.jdesktop.swingx.image;
036
037import java.awt.image.BufferedImage;
038
039import org.jdesktop.swingx.util.GraphicsUtilities;
040
041/**
042 * <p>A fast blur filter can be used to blur pictures quickly. This filter is an
043 * implementation of the box blur algorithm. The blurs generated by this
044 * algorithm might show square artifacts, especially on pictures containing
045 * straight lines (rectangles, text, etc.) On most pictures though, the
046 * result will look very good.</p>
047 * <p>The force of the blur can be controlled with a radius and the
048 * default radius is 3. Since the blur clamps values on the edges of the
049 * source picture, you might need to provide a picture with empty borders
050 * to avoid artifacts at the edges. The performance of this filter are
051 * independent from the radius.</p>
052 *
053 * @author Romain Guy <romain.guy@mac.com>
054 */
055public class FastBlurFilter extends AbstractFilter {
056    private final int radius;
057
058    /**
059     * <p>Creates a new blur filter with a default radius of 3.</p>
060     */
061    public FastBlurFilter() {
062        this(3);
063    }
064
065    /**
066     * <p>Creates a new blur filter with the specified radius. If the radius
067     * is lower than 1, a radius of 1 will be used automatically.</p>
068     *
069     * @param radius the radius, in pixels, of the blur
070     */
071    public FastBlurFilter(int radius) {
072        if (radius < 1) {
073            radius = 1;
074        }
075
076        this.radius = radius;
077    }
078
079    /**
080     * <p>Returns the radius used by this filter, in pixels.</p>
081     *
082     * @return the radius of the blur
083     */
084    public int getRadius() {
085        return radius;
086    }
087
088    /**
089     * {@inheritDoc}
090     */
091    @Override
092    public BufferedImage filter(BufferedImage src, BufferedImage dst) {
093        int width = src.getWidth();
094        int height = src.getHeight();
095
096        if (dst == null) {
097            dst = createCompatibleDestImage(src, null);
098        }
099
100        int[] srcPixels = new int[width * height];
101        int[] dstPixels = new int[width * height];
102
103        GraphicsUtilities.getPixels(src, 0, 0, width, height, srcPixels);
104        // horizontal pass
105        blur(srcPixels, dstPixels, width, height, radius);
106        // vertical pass
107        //noinspection SuspiciousNameCombination
108        blur(dstPixels, srcPixels, height, width, radius);
109        // the result is now stored in srcPixels due to the 2nd pass
110        GraphicsUtilities.setPixels(dst, 0, 0, width, height, srcPixels);
111
112        return dst;
113    }
114
115    /**
116     * <p>Blurs the source pixels into the destination pixels. The force of
117     * the blur is specified by the radius which must be greater than 0.</p>
118     * <p>The source and destination pixels arrays are expected to be in the
119     * INT_ARGB format.</p>
120     * <p>After this method is executed, dstPixels contains a transposed and
121     * filtered copy of srcPixels.</p>
122     *
123     * @param srcPixels the source pixels
124     * @param dstPixels the destination pixels
125     * @param width the width of the source picture
126     * @param height the height of the source picture
127     * @param radius the radius of the blur effect
128     */
129    static void blur(int[] srcPixels, int[] dstPixels,
130                     int width, int height, int radius) {
131        final int windowSize = radius * 2 + 1;
132        final int radiusPlusOne = radius + 1;
133
134        int sumAlpha;
135        int sumRed;
136        int sumGreen;
137        int sumBlue;
138
139        int srcIndex = 0;
140        int dstIndex;
141        int pixel;
142
143        int[] sumLookupTable = new int[256 * windowSize];
144        for (int i = 0; i < sumLookupTable.length; i++) {
145            sumLookupTable[i] = i / windowSize;
146        }
147
148        int[] indexLookupTable = new int[radiusPlusOne];
149        if (radius < width) {
150            for (int i = 0; i < indexLookupTable.length; i++) {
151                indexLookupTable[i] = i;
152            }
153        } else {
154            for (int i = 0; i < width; i++) {
155                indexLookupTable[i] = i;
156            }
157            for (int i = width; i < indexLookupTable.length; i++) {
158                indexLookupTable[i] = width - 1;
159            }
160        }
161
162        for (int y = 0; y < height; y++) {
163            sumAlpha = sumRed = sumGreen = sumBlue = 0;
164            dstIndex = y;
165
166            pixel = srcPixels[srcIndex];
167            sumAlpha += radiusPlusOne * ((pixel >> 24) & 0xFF);
168            sumRed   += radiusPlusOne * ((pixel >> 16) & 0xFF);
169            sumGreen += radiusPlusOne * ((pixel >>  8) & 0xFF);
170            sumBlue  += radiusPlusOne * ( pixel        & 0xFF);
171
172            for (int i = 1; i <= radius; i++) {
173                pixel = srcPixels[srcIndex + indexLookupTable[i]];
174                sumAlpha += (pixel >> 24) & 0xFF;
175                sumRed   += (pixel >> 16) & 0xFF;
176                sumGreen += (pixel >>  8) & 0xFF;
177                sumBlue  +=  pixel        & 0xFF;
178            }
179
180            for  (int x = 0; x < width; x++) {
181                dstPixels[dstIndex] = sumLookupTable[sumAlpha] << 24 |
182                                      sumLookupTable[sumRed]   << 16 |
183                                      sumLookupTable[sumGreen] <<  8 |
184                                      sumLookupTable[sumBlue];
185                dstIndex += height;
186
187                int nextPixelIndex = x + radiusPlusOne;
188                if (nextPixelIndex >= width) {
189                    nextPixelIndex = width - 1;
190                }
191
192                int previousPixelIndex = x - radius;
193                if (previousPixelIndex < 0) {
194                    previousPixelIndex = 0;
195                }
196
197                int nextPixel = srcPixels[srcIndex + nextPixelIndex];
198                int previousPixel = srcPixels[srcIndex + previousPixelIndex];
199
200                sumAlpha += (nextPixel     >> 24) & 0xFF;
201                sumAlpha -= (previousPixel >> 24) & 0xFF;
202
203                sumRed += (nextPixel     >> 16) & 0xFF;
204                sumRed -= (previousPixel >> 16) & 0xFF;
205
206                sumGreen += (nextPixel     >> 8) & 0xFF;
207                sumGreen -= (previousPixel >> 8) & 0xFF;
208
209                sumBlue += nextPixel & 0xFF;
210                sumBlue -= previousPixel & 0xFF;
211            }
212
213            srcIndex += width;
214        }
215    }
216}