| 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 thresholded blur for ironing out wrinkles. |
| 10 | * |
| 11 | * @author Jerry Huxtable |
| 12 | */ |
| 13 | public class SmartBlurFilter extends AbstractBufferedImageOp |
| 14 | { |
| 15 | private static final int DEFAULT_RADIUS = 5; |
| 16 | private static final int DEFAULT_THRESHOLD = 10; |
| 17 | |
| 18 | private int hRadius; |
| 19 | private int threshold; |
| 20 | private int vRadius; |
| 21 | |
| 22 | public SmartBlurFilter(int radius, int threshold) { |
| 23 | super(); |
| 24 | setRadius(radius); |
| 25 | setThreshold(threshold); |
| 26 | } |
| 27 | |
| 28 | public SmartBlurFilter() { |
| 29 | this(DEFAULT_RADIUS, DEFAULT_THRESHOLD); |
| 30 | } |
| 31 | |
| 32 | public void setHRadius(int hRadius) { |
| 33 | this.hRadius = hRadius; |
| 34 | } |
| 35 | |
| 36 | public void setRadius(int radius) { |
| 37 | this.hRadius = this.vRadius = radius; |
| 38 | } |
| 39 | |
| 40 | public void setThreshold(int threshold) { |
| 41 | this.threshold = threshold; |
| 42 | } |
| 43 | |
| 44 | public void setVRadius(int vRadius) { |
| 45 | this.vRadius = vRadius; |
| 46 | } |
| 47 | |
| 48 | public int getHRadius() { |
| 49 | return hRadius; |
| 50 | } |
| 51 | |
| 52 | public int getRadius() { |
| 53 | return hRadius; |
| 54 | } |
| 55 | |
| 56 | public int getThreshold() { |
| 57 | return threshold; |
| 58 | } |
| 59 | |
| 60 | public int getVRadius() { |
| 61 | return vRadius; |
| 62 | } |
| 63 | |
| 64 | public BufferedImage filter(BufferedImage src, BufferedImage dst) { |
| 65 | int width = src.getWidth(); |
| 66 | int height = src.getHeight(); |
| 67 | |
| 68 | if (dst == null) { |
| 69 | dst = createCompatibleDestImage(src, null); |
| 70 | } |
| 71 | |
| 72 | int[] inPixels = new int[width * height]; |
| 73 | int[] outPixels = new int[width * height]; |
| 74 | |
| 75 | getRGB(src, 0, 0, width, height, inPixels); |
| 76 | |
| 77 | Kernel kernel = GaussianFilter.makeKernel(hRadius); |
| 78 | |
| 79 | thresholdBlur(kernel, inPixels, outPixels, width, height, true); |
| 80 | thresholdBlur(kernel, outPixels, inPixels, height, width, true); |
| 81 | |
| 82 | setRGB(dst, 0, 0, width, height, inPixels); |
| 83 | return dst; |
| 84 | } |
| 85 | |
| 86 | /** |
| 87 | * Convolve with a kernel consisting of one row. |
| 88 | */ |
| 89 | public void thresholdBlur(Kernel kernel, int[] inPixels, int[] outPixels, int width, int height, boolean alpha) { |
| 90 | float[] matrix = kernel.getKernelData(null); |
| 91 | int cols = kernel.getWidth(); |
| 92 | int cols2 = cols / 2; |
| 93 | |
| 94 | for (int y = 0; y < height; y++) { |
| 95 | int ioffset = y * width; |
| 96 | int outIndex = y; |
| 97 | |
| 98 | for (int x = 0; x < width; x++) { |
| 99 | float r = 0; |
| 100 | float g = 0; |
| 101 | float b = 0; |
| 102 | float a = 0; |
| 103 | int moffset = cols2; |
| 104 | |
| 105 | int rgb1 = inPixels[ioffset + x]; |
| 106 | int a1 = (rgb1 >> 24) & 0xff; |
| 107 | int r1 = (rgb1 >> 16) & 0xff; |
| 108 | int g1 = (rgb1 >> 8) & 0xff; |
| 109 | int b1 = rgb1 & 0xff; |
| 110 | float af = 0; |
| 111 | float rf = 0; |
| 112 | float gf = 0; |
| 113 | float bf = 0; |
| 114 | |
| 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 | |
| 121 | if (!(0 <= ix && ix < width)) { |
| 122 | ix = x; |
| 123 | } |
| 124 | int rgb2 = inPixels[ioffset + ix]; |
| 125 | int a2 = (rgb2 >> 24) & 0xff; |
| 126 | int r2 = (rgb2 >> 16) & 0xff; |
| 127 | int g2 = (rgb2 >> 8) & 0xff; |
| 128 | int b2 = rgb2 & 0xff; |
| 129 | |
| 130 | int d; |
| 131 | |
| 132 | d = a1 - a2; |
| 133 | if (d >= -threshold && d <= threshold) { |
| 134 | a += f * a2; |
| 135 | af += f; |
| 136 | } |
| 137 | d = r1 - r2; |
| 138 | if (d >= -threshold && d <= threshold) { |
| 139 | r += f * r2; |
| 140 | rf += f; |
| 141 | } |
| 142 | d = g1 - g2; |
| 143 | if (d >= -threshold && d <= threshold) { |
| 144 | g += f * g2; |
| 145 | gf += f; |
| 146 | } |
| 147 | d = b1 - b2; |
| 148 | if (d >= -threshold && d <= threshold) { |
| 149 | b += f * b2; |
| 150 | bf += f; |
| 151 | } |
| 152 | } |
| 153 | } |
| 154 | a = af == 0 ? a1 : a / af; |
| 155 | r = rf == 0 ? r1 : r / rf; |
| 156 | g = gf == 0 ? g1 : g / gf; |
| 157 | b = bf == 0 ? b1 : b / bf; |
| 158 | |
| 159 | int ia = alpha ? ImageMath.clamp((int) (a + 0.5)) : 0xff; |
| 160 | int ir = ImageMath.clamp((int) (r + 0.5)); |
| 161 | int ig = ImageMath.clamp((int) (g + 0.5)); |
| 162 | int ib = ImageMath.clamp((int) (b + 0.5)); |
| 163 | |
| 164 | outPixels[outIndex] = (ia << 24) | (ir << 16) | (ig << 8) | ib; |
| 165 | outIndex += height; |
| 166 | } |
| 167 | } |
| 168 | } |
| 169 | |
| 170 | public String toString() { |
| 171 | return "Blur/Smart Blur..."; |
| 172 | } |
| 173 | } |