1 | // Copyright 2005 Huxtable.com. All rights reserved. |
2 | // For more information, see <http://www.jhlabs.com/ip/blurring.html>. |
3 | package com.jhlabs.image; |
4 | |
5 | import java.awt.image.BufferedImage; |
6 | import java.awt.image.Kernel; |
7 | |
8 | /** |
9 | * A filter which applies Gaussian blur to an image. This is a subclass of ConvolveFilter which |
10 | * simply creates a kernel with a Gaussian distribution for blurring. |
11 | * |
12 | * @author Jerry Huxtable |
13 | */ |
14 | public class GaussianFilter extends ConvolveFilter |
15 | { |
16 | static final long serialVersionUID = 5377089073023183684L; |
17 | |
18 | private float radius; |
19 | |
20 | /** |
21 | * Construct a Gaussian filter. |
22 | */ |
23 | public GaussianFilter() { |
24 | this(2); |
25 | } |
26 | |
27 | /** |
28 | * Construct a Gaussian filter. |
29 | * |
30 | * @param radius blur radius in pixels |
31 | */ |
32 | public GaussianFilter(float radius) { |
33 | setRadius(radius); |
34 | } |
35 | |
36 | /** |
37 | * Set the radius of the kernel, and hence the amount of blur. The bigger the radius, the |
38 | * longer this filter will take. |
39 | * |
40 | * @param radius the radius of the blur in pixels. |
41 | */ |
42 | public void setRadius(float radius) { |
43 | setKernel(makeKernel(radius)); |
44 | this.radius = radius; |
45 | } |
46 | |
47 | /** |
48 | * Get the radius of the kernel. |
49 | * |
50 | * @return the radius |
51 | */ |
52 | public float getRadius() { |
53 | return radius; |
54 | } |
55 | |
56 | public static void convolveAndTranspose(Kernel kernel, int[] inPixels, int[] outPixels, int width, int height, |
57 | boolean alpha, int edgeAction) { |
58 | float[] matrix = kernel.getKernelData(null); |
59 | int cols = kernel.getWidth(); |
60 | int cols2 = cols / 2; |
61 | |
62 | for (int y = 0; y < height; y++) { |
63 | int index = y; |
64 | int ioffset = y * width; |
65 | |
66 | for (int x = 0; x < width; x++) { |
67 | float r = 0; |
68 | float g = 0; |
69 | float b = 0; |
70 | float a = 0; |
71 | int moffset = cols2; |
72 | |
73 | for (int col = -cols2; col <= cols2; col++) { |
74 | float f = matrix[moffset + col]; |
75 | |
76 | if (f != 0) { |
77 | int ix = x + col; |
78 | |
79 | if (ix < 0) { |
80 | if (edgeAction == CLAMP_EDGES) { |
81 | ix = 0; |
82 | } else if (edgeAction == WRAP_EDGES) { |
83 | ix = (x + width) % width; |
84 | } |
85 | } else if (ix >= width) { |
86 | if (edgeAction == CLAMP_EDGES) { |
87 | ix = width - 1; |
88 | } else if (edgeAction == WRAP_EDGES) { |
89 | ix = (x + width) % width; |
90 | } |
91 | } |
92 | int rgb = inPixels[ioffset + ix]; |
93 | |
94 | a += f * ((rgb >> 24) & 0xff); |
95 | r += f * ((rgb >> 16) & 0xff); |
96 | g += f * ((rgb >> 8) & 0xff); |
97 | b += f * (rgb & 0xff); |
98 | } |
99 | } |
100 | |
101 | int ia = alpha ? ImageMath.clamp((int) (a + 0.5)) : 0xff; |
102 | int ir = ImageMath.clamp((int) (r + 0.5)); |
103 | int ig = ImageMath.clamp((int) (g + 0.5)); |
104 | int ib = ImageMath.clamp((int) (b + 0.5)); |
105 | |
106 | outPixels[index] = (ia << 24) | (ir << 16) | (ig << 8) | ib; |
107 | index += height; |
108 | } |
109 | } |
110 | } |
111 | |
112 | /** |
113 | * Make a Gaussian blur kernel. |
114 | */ |
115 | public static Kernel makeKernel(float radius) { |
116 | int r = (int) Math.ceil(radius); |
117 | int rows = r * 2 + 1; |
118 | float[] matrix = new float[rows]; |
119 | float sigma = radius / 3; |
120 | float sigma22 = 2 * sigma * sigma; |
121 | float sigmaPi2 = 2 * ImageMath.PI * sigma; |
122 | float sqrtSigmaPi2 = (float) Math.sqrt(sigmaPi2); |
123 | float radius2 = radius * radius; |
124 | float total = 0; |
125 | int index = 0; |
126 | |
127 | for (int row = -r; row <= r; row++) { |
128 | float distance = row * row; |
129 | |
130 | if (distance > radius2) { |
131 | matrix[index] = 0; |
132 | } else { |
133 | matrix[index] = (float) Math.exp(-(distance) / sigma22) / sqrtSigmaPi2; |
134 | } |
135 | total += matrix[index]; |
136 | index++; |
137 | } |
138 | for (int i = 0; i < rows; i++) { |
139 | matrix[i] /= total; |
140 | } |
141 | |
142 | return new Kernel(rows, 1, matrix); |
143 | } |
144 | |
145 | public BufferedImage filter(BufferedImage src, BufferedImage dst) { |
146 | int width = src.getWidth(); |
147 | int height = src.getHeight(); |
148 | |
149 | if (dst == null) { |
150 | dst = createCompatibleDestImage(src, null); |
151 | } |
152 | |
153 | int[] inPixels = new int[width * height]; |
154 | int[] outPixels = new int[width * height]; |
155 | |
156 | src.getRGB(0, 0, width, height, inPixels, 0, width); |
157 | |
158 | convolveAndTranspose(getKernel(), inPixels, outPixels, width, height, hasAlpha(), CLAMP_EDGES); |
159 | convolveAndTranspose(getKernel(), outPixels, inPixels, height, width, hasAlpha(), CLAMP_EDGES); |
160 | |
161 | dst.setRGB(0, 0, width, height, inPixels, 0, width); |
162 | return dst; |
163 | } |
164 | |
165 | public String toString() { |
166 | return "GaussianBlur(radius=" + getRadius() + ")"; |
167 | } |
168 | } |