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.Rectangle; |
6 | import java.awt.RenderingHints; |
7 | import java.awt.geom.Point2D; |
8 | import java.awt.geom.Rectangle2D; |
9 | import java.awt.image.BufferedImage; |
10 | import java.awt.image.ColorModel; |
11 | import java.awt.image.Kernel; |
12 | |
13 | /** |
14 | * A filter which applies a convolution kernel to an image. |
15 | * |
16 | * @author Jerry Huxtable |
17 | */ |
18 | public class ConvolveFilter extends AbstractBufferedImageOp |
19 | { |
20 | public static final int CLAMP_EDGES = 1; |
21 | public static final int WRAP_EDGES = 2; |
22 | public static final int ZERO_EDGES = 0; |
23 | static final long serialVersionUID = 2239251672685254626L; |
24 | |
25 | private boolean alpha = true; |
26 | private int edgeAction = CLAMP_EDGES; |
27 | private Kernel kernel; |
28 | |
29 | /** |
30 | * Construct a filter with a null kernel. This is only useful if you're going to change the |
31 | * kernel later on. |
32 | */ |
33 | public ConvolveFilter() { |
34 | this(new float[9]); |
35 | } |
36 | |
37 | /** |
38 | * Construct a filter with the given 3x3 kernel. |
39 | * |
40 | * @param matrix an array of 9 floats containing the kernel |
41 | */ |
42 | public ConvolveFilter(float[] matrix) { |
43 | this(new Kernel(3, 3, matrix)); |
44 | } |
45 | |
46 | /** |
47 | * Construct a filter with the given kernel. |
48 | * |
49 | * @param rows the number of rows in the kernel |
50 | * @param cols the number of columns in the kernel |
51 | * @param matrix an array of rows*cols floats containing the kernel |
52 | */ |
53 | public ConvolveFilter(int rows, int cols, float[] matrix) { |
54 | this(new Kernel(cols, rows, matrix)); |
55 | } |
56 | |
57 | /** |
58 | * Construct a filter with the given 3x3 kernel. |
59 | * |
60 | * @param kernel Description of the parameter |
61 | */ |
62 | public ConvolveFilter(Kernel kernel) { |
63 | this.kernel = kernel; |
64 | } |
65 | |
66 | public void setEdgeAction(int edgeAction) { |
67 | this.edgeAction = edgeAction; |
68 | } |
69 | |
70 | public void setKernel(Kernel kernel) { |
71 | this.kernel = kernel; |
72 | } |
73 | |
74 | public Rectangle2D getBounds2D(BufferedImage src) { |
75 | return new Rectangle(0, 0, src.getWidth(), src.getHeight()); |
76 | } |
77 | |
78 | public int getEdgeAction() { |
79 | return edgeAction; |
80 | } |
81 | |
82 | public Kernel getKernel() { |
83 | return kernel; |
84 | } |
85 | |
86 | public Point2D getPoint2D(Point2D srcPt, Point2D dstPt) { |
87 | if (dstPt == null) { |
88 | dstPt = new Point2D.Double(); |
89 | } |
90 | dstPt.setLocation(srcPt.getX(), srcPt.getY()); |
91 | return dstPt; |
92 | } |
93 | |
94 | public RenderingHints getRenderingHints() { |
95 | return null; |
96 | } |
97 | |
98 | public static void convolve(Kernel kernel, int[] inPixels, int[] outPixels, int width, int height, int edgeAction) { |
99 | convolve(kernel, inPixels, outPixels, width, height, true, edgeAction); |
100 | } |
101 | |
102 | public static void convolve(Kernel kernel, int[] inPixels, int[] outPixels, int width, int height, boolean alpha, |
103 | int edgeAction) { |
104 | if (kernel.getHeight() == 1) { |
105 | convolveH(kernel, inPixels, outPixels, width, height, alpha, edgeAction); |
106 | } else if (kernel.getWidth() == 1) { |
107 | convolveV(kernel, inPixels, outPixels, width, height, alpha, edgeAction); |
108 | } else { |
109 | convolveHV(kernel, inPixels, outPixels, width, height, alpha, edgeAction); |
110 | } |
111 | } |
112 | |
113 | /** |
114 | * Convolve with a kernel consisting of one row. |
115 | */ |
116 | public static void convolveH(Kernel kernel, int[] inPixels, int[] outPixels, int width, int height, |
117 | boolean alpha, int edgeAction) { |
118 | int index = 0; |
119 | float[] matrix = kernel.getKernelData(null); |
120 | int cols = kernel.getWidth(); |
121 | int cols2 = cols / 2; |
122 | |
123 | for (int y = 0; y < height; y++) { |
124 | int ioffset = y * width; |
125 | |
126 | for (int x = 0; x < width; x++) { |
127 | float r = 0; |
128 | float g = 0; |
129 | float b = 0; |
130 | float a = 0; |
131 | int moffset = cols2; |
132 | |
133 | for (int col = -cols2; col <= cols2; col++) { |
134 | float f = matrix[moffset + col]; |
135 | |
136 | if (f != 0) { |
137 | int ix = x + col; |
138 | |
139 | if (ix < 0) { |
140 | if (edgeAction == CLAMP_EDGES) { |
141 | ix = 0; |
142 | } else if (edgeAction == WRAP_EDGES) { |
143 | ix = (x + width) % width; |
144 | } |
145 | } else if (ix >= width) { |
146 | if (edgeAction == CLAMP_EDGES) { |
147 | ix = width - 1; |
148 | } else if (edgeAction == WRAP_EDGES) { |
149 | ix = (x + width) % width; |
150 | } |
151 | } |
152 | int rgb = inPixels[ioffset + ix]; |
153 | |
154 | a += f * ((rgb >> 24) & 0xff); |
155 | r += f * ((rgb >> 16) & 0xff); |
156 | g += f * ((rgb >> 8) & 0xff); |
157 | b += f * (rgb & 0xff); |
158 | } |
159 | } |
160 | |
161 | int ia = alpha ? ImageMath.clamp((int) (a + 0.5)) : 0xff; |
162 | int ir = ImageMath.clamp((int) (r + 0.5)); |
163 | int ig = ImageMath.clamp((int) (g + 0.5)); |
164 | int ib = ImageMath.clamp((int) (b + 0.5)); |
165 | |
166 | outPixels[index++] = (ia << 24) | (ir << 16) | (ig << 8) | ib; |
167 | } |
168 | } |
169 | } |
170 | |
171 | /** |
172 | * Convolve with a 2D kernel. |
173 | */ |
174 | public static void convolveHV(Kernel kernel, int[] inPixels, int[] outPixels, int width, int height, |
175 | boolean alpha, int edgeAction) { |
176 | int index = 0; |
177 | float[] matrix = kernel.getKernelData(null); |
178 | int rows = kernel.getHeight(); |
179 | int cols = kernel.getWidth(); |
180 | int rows2 = rows / 2; |
181 | int cols2 = cols / 2; |
182 | |
183 | for (int y = 0; y < height; y++) { |
184 | for (int x = 0; x < width; x++) { |
185 | float r = 0; |
186 | float g = 0; |
187 | float b = 0; |
188 | float a = 0; |
189 | |
190 | for (int row = -rows2; row <= rows2; row++) { |
191 | int iy = y + row; |
192 | int ioffset; |
193 | |
194 | if (0 <= iy && iy < height) { |
195 | ioffset = iy * width; |
196 | } else if (edgeAction == CLAMP_EDGES) { |
197 | ioffset = y * width; |
198 | } else if (edgeAction == WRAP_EDGES) { |
199 | ioffset = ((iy + height) % height) * width; |
200 | } else { |
201 | continue; |
202 | } |
203 | |
204 | int moffset = cols * (row + rows2) + cols2; |
205 | |
206 | for (int col = -cols2; col <= cols2; col++) { |
207 | float f = matrix[moffset + col]; |
208 | |
209 | if (f != 0) { |
210 | int ix = x + col; |
211 | |
212 | if (!(0 <= ix && ix < width)) { |
213 | if (edgeAction == CLAMP_EDGES) { |
214 | ix = x; |
215 | } else if (edgeAction == WRAP_EDGES) { |
216 | ix = (x + width) % width; |
217 | } else { |
218 | continue; |
219 | } |
220 | } |
221 | int rgb = inPixels[ioffset + ix]; |
222 | |
223 | a += f * ((rgb >> 24) & 0xff); |
224 | r += f * ((rgb >> 16) & 0xff); |
225 | g += f * ((rgb >> 8) & 0xff); |
226 | b += f * (rgb & 0xff); |
227 | } |
228 | } |
229 | } |
230 | |
231 | int ia = alpha ? ImageMath.clamp((int) (a + 0.5)) : 0xff; |
232 | int ir = ImageMath.clamp((int) (r + 0.5)); |
233 | int ig = ImageMath.clamp((int) (g + 0.5)); |
234 | int ib = ImageMath.clamp((int) (b + 0.5)); |
235 | |
236 | outPixels[index++] = (ia << 24) | (ir << 16) | (ig << 8) | ib; |
237 | } |
238 | } |
239 | } |
240 | |
241 | /** |
242 | * Convolve with a kernel consisting of one column. |
243 | */ |
244 | public static void convolveV(Kernel kernel, int[] inPixels, int[] outPixels, int width, int height, |
245 | boolean alpha, int edgeAction) { |
246 | int index = 0; |
247 | float[] matrix = kernel.getKernelData(null); |
248 | int rows = kernel.getHeight(); |
249 | int rows2 = rows / 2; |
250 | |
251 | for (int y = 0; y < height; y++) { |
252 | for (int x = 0; x < width; x++) { |
253 | float r = 0; |
254 | float g = 0; |
255 | float b = 0; |
256 | float a = 0; |
257 | |
258 | for (int row = -rows2; row <= rows2; row++) { |
259 | int iy = y + row; |
260 | int ioffset; |
261 | |
262 | if (iy < 0) { |
263 | if (edgeAction == CLAMP_EDGES) { |
264 | ioffset = 0; |
265 | } else if (edgeAction == WRAP_EDGES) { |
266 | ioffset = ((y + height) % height) * width; |
267 | } else { |
268 | ioffset = iy * width; |
269 | } |
270 | } else if (iy >= height) { |
271 | if (edgeAction == CLAMP_EDGES) { |
272 | ioffset = (height - 1) * width; |
273 | } else if (edgeAction == WRAP_EDGES) { |
274 | ioffset = ((y + height) % height) * width; |
275 | } else { |
276 | ioffset = iy * width; |
277 | } |
278 | } else { |
279 | ioffset = iy * width; |
280 | } |
281 | |
282 | float f = matrix[row + rows2]; |
283 | |
284 | if (f != 0) { |
285 | int rgb = inPixels[ioffset + x]; |
286 | |
287 | a += f * ((rgb >> 24) & 0xff); |
288 | r += f * ((rgb >> 16) & 0xff); |
289 | g += f * ((rgb >> 8) & 0xff); |
290 | b += f * (rgb & 0xff); |
291 | } |
292 | } |
293 | |
294 | int ia = alpha ? ImageMath.clamp((int) (a + 0.5)) : 0xff; |
295 | int ir = ImageMath.clamp((int) (r + 0.5)); |
296 | int ig = ImageMath.clamp((int) (g + 0.5)); |
297 | int ib = ImageMath.clamp((int) (b + 0.5)); |
298 | |
299 | outPixels[index++] = (ia << 24) | (ir << 16) | (ig << 8) | ib; |
300 | } |
301 | } |
302 | } |
303 | |
304 | public BufferedImage createCompatibleDestImage(BufferedImage src, ColorModel dstCM) { |
305 | if (dstCM == null) { |
306 | dstCM = src.getColorModel(); |
307 | } |
308 | return new BufferedImage( |
309 | dstCM, |
310 | dstCM.createCompatibleWritableRaster(src.getWidth(), src.getHeight()), |
311 | dstCM.isAlphaPremultiplied(), |
312 | null); |
313 | } |
314 | |
315 | public BufferedImage filter(BufferedImage src, BufferedImage dst) { |
316 | int width = src.getWidth(); |
317 | int height = src.getHeight(); |
318 | |
319 | if (dst == null) { |
320 | dst = createCompatibleDestImage(src, null); |
321 | } |
322 | |
323 | int[] inPixels = new int[width * height]; |
324 | int[] outPixels = new int[width * height]; |
325 | |
326 | getRGB(src, 0, 0, width, height, inPixels); |
327 | |
328 | convolve(kernel, inPixels, outPixels, width, height, alpha, edgeAction); |
329 | |
330 | setRGB(dst, 0, 0, width, height, outPixels); |
331 | return dst; |
332 | } |
333 | |
334 | public boolean hasAlpha() { |
335 | return alpha; |
336 | } |
337 | |
338 | public String toString() { |
339 | return "Blur/Convolve..."; |
340 | } |
341 | } |