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}