package net.bull.javamelody.swing.util; import java.awt.image.BufferedImage; import java.awt.image.Raster; import java.awt.image.WritableRaster; /** * <p>A fast blur filter can be used to blur pictures quickly. This filter is an * implementation of the box blur algorithm. The blurs generated by this * algorithm might show square artifacts, especially on pictures containing * straight lines (rectangles, text, etc.) On most pictures though, the * result will look very good.</p> * <p>The force of the blur can be controlled with a radius and the * default radius is 3. Since the blur clamps values on the edges of the * source picture, you might need to provide a picture with empty borders * to avoid artifacts at the edges. The performance of this filter are * independant from the radius.</p> * * @author Romain Guy, romain.guy@mac.com */ public class FastBlurFilter extends AbstractFilter { private final int radius; /** * <p>Creates a new blur filter with a default radius of 3.</p> */ public FastBlurFilter() { this(3); } /** * <p>Creates a new blur filter with the specified radius. If the radius * is lower than 1, a radius of 1 will be used automatically.</p> * * @param radius the radius, in pixels, of the blur */ public FastBlurFilter(int radius) { super(); if (radius < 1) { this.radius = 1; } else { this.radius = radius; } } /** * <p>Returns the radius used by this filter, in pixels.</p> * * @return the radius of the blur */ public int getRadius() { return radius; } /** * {@inheritDoc} */ @Override public BufferedImage filter(BufferedImage src, BufferedImage dst) { final int width = src.getWidth(); final int height = src.getHeight(); final BufferedImage image; if (dst == null) { image = createCompatibleDestImage(src, null); } else { image = dst; } final int[] srcPixels = new int[width * height]; final int[] dstPixels = new int[width * height]; getPixels(src, 0, 0, width, height, srcPixels); // horizontal pass blur(srcPixels, dstPixels, width, height, radius); // vertical pass blur(dstPixels, srcPixels, height, width, radius); // the result is now stored in srcPixels due to the 2nd pass setPixels(image, 0, 0, width, height, srcPixels); return image; } /** * <p>Returns an array of pixels, stored as integers, from a * <code>BufferedImage</code>. The pixels are grabbed from a rectangular * area defined by a location and two dimensions. Calling this method on * an image of type different from <code>BufferedImage.TYPE_INT_ARGB</code> * and <code>BufferedImage.TYPE_INT_RGB</code> will unmanage the image.</p> * * @param img the source image * @param x the x location at which to start grabbing pixels * @param y the y location at which to start grabbing pixels * @param w the width of the rectangle of pixels to grab * @param h the height of the rectangle of pixels to grab * @param pixels a pre-allocated array of pixels of size w*h; can be null * @return <code>pixels</code> if non-null, a new array of integers * otherwise * @throws IllegalArgumentException is <code>pixels</code> is non-null and * of length < w*h */ private static int[] getPixels(BufferedImage img, int x, int y, int w, int h, int[] pixels) { if (w == 0 || h == 0) { return new int[0]; } final int[] pix; if (pixels == null) { pix = new int[w * h]; } else if (pixels.length < w * h) { throw new IllegalArgumentException("pixels array must have a length >= w*h"); } else { pix = pixels; } final int imageType = img.getType(); if (imageType == BufferedImage.TYPE_INT_ARGB || imageType == BufferedImage.TYPE_INT_RGB) { final Raster raster = img.getRaster(); return (int[]) raster.getDataElements(x, y, w, h, pix); } // Unmanages the image return img.getRGB(x, y, w, h, pix, 0, w); } /** * <p>Writes a rectangular area of pixels in the destination * <code>BufferedImage</code>. Calling this method on * an image of type different from <code>BufferedImage.TYPE_INT_ARGB</code> * and <code>BufferedImage.TYPE_INT_RGB</code> will unmanage the image.</p> * * @param img the destination image * @param x the x location at which to start storing pixels * @param y the y location at which to start storing pixels * @param w the width of the rectangle of pixels to store * @param h the height of the rectangle of pixels to store * @param pixels an array of pixels, stored as integers * @throws IllegalArgumentException is <code>pixels</code> is non-null and * of length < w*h */ private static void setPixels(BufferedImage img, int x, int y, int w, int h, int[] pixels) { if (pixels == null || w == 0 || h == 0) { return; } else if (pixels.length < w * h) { throw new IllegalArgumentException("pixels array must have a length >= w*h"); } final int imageType = img.getType(); if (imageType == BufferedImage.TYPE_INT_ARGB || imageType == BufferedImage.TYPE_INT_RGB) { final WritableRaster raster = img.getRaster(); raster.setDataElements(x, y, w, h, pixels); } else { // Unmanages the image img.setRGB(x, y, w, h, pixels, 0, w); } } /** * <p>Blurs the source pixels into the destination pixels. The force of * the blur is specified by the radius which must be greater than 0.</p> * <p>The source and destination pixels arrays are expected to be in the * INT_ARGB format.</p> * * @param srcPixels the source pixels * @param dstPixels the destination pixels * @param width the width of the source picture * @param height the height of the source picture * @param radius the radius of the blur effect */ static void blur(int[] srcPixels, int[] dstPixels, int width, int height, int radius) { final int windowSize = radius * 2 + 1; final int radiusPlusOne = radius + 1; int sumAlpha; int sumRed; int sumGreen; int sumBlue; int srcIndex = 0; int dstIndex; int pixel; final int[] sumLookupTable = createSumLookupTable(windowSize); final int[] indexLookupTable = createIndexLookupTable(width, radius); for (int y = 0; y < height; y++) { sumAlpha = 0; sumRed = 0; sumGreen = 0; sumBlue = 0; dstIndex = y; pixel = srcPixels[srcIndex]; sumAlpha += radiusPlusOne * (pixel >> 24 & 0xFF); sumRed += radiusPlusOne * (pixel >> 16 & 0xFF); sumGreen += radiusPlusOne * (pixel >> 8 & 0xFF); sumBlue += radiusPlusOne * (pixel & 0xFF); for (int i = 1; i <= radius; i++) { pixel = srcPixels[srcIndex + indexLookupTable[i]]; sumAlpha += pixel >> 24 & 0xFF; sumRed += pixel >> 16 & 0xFF; sumGreen += pixel >> 8 & 0xFF; sumBlue += pixel & 0xFF; } for (int x = 0; x < width; x++) { dstPixels[dstIndex] = sumLookupTable[sumAlpha] << 24 | sumLookupTable[sumRed] << 16 | sumLookupTable[sumGreen] << 8 | sumLookupTable[sumBlue]; dstIndex += height; int nextPixelIndex = x + radiusPlusOne; if (nextPixelIndex >= width) { nextPixelIndex = width - 1; } int previousPixelIndex = x - radius; if (previousPixelIndex < 0) { previousPixelIndex = 0; } final int nextPixel = srcPixels[srcIndex + nextPixelIndex]; final int previousPixel = srcPixels[srcIndex + previousPixelIndex]; sumAlpha += nextPixel >> 24 & 0xFF; sumAlpha -= previousPixel >> 24 & 0xFF; sumRed += nextPixel >> 16 & 0xFF; sumRed -= previousPixel >> 16 & 0xFF; sumGreen += nextPixel >> 8 & 0xFF; sumGreen -= previousPixel >> 8 & 0xFF; sumBlue += nextPixel & 0xFF; sumBlue -= previousPixel & 0xFF; } srcIndex += width; } } private static int[] createIndexLookupTable(int width, int radius) { final int radiusPlusOne = radius + 1; final int[] indexLookupTable = new int[radiusPlusOne]; if (radius < width) { for (int i = 0; i < indexLookupTable.length; i++) { indexLookupTable[i] = i; } } else { for (int i = 0; i < width; i++) { indexLookupTable[i] = i; } for (int i = width; i < indexLookupTable.length; i++) { indexLookupTable[i] = width - 1; } } return indexLookupTable; } private static int[] createSumLookupTable(final int windowSize) { final int[] sumLookupTable = new int[256 * windowSize]; for (int i = 0; i < sumLookupTable.length; i++) { sumLookupTable[i] = i / windowSize; } return sumLookupTable; } }