1

I need to implement Gaussian Blur in Java for 3x3, 5x5 and 7x7 matrix. Can you correct me if I'm wrong:

  1. I've a matrix(M) 3x3 (middle value is M(0, 0)):

    1 2 1  
    2 4 2  
    1 2 1  
    
  2. I take one pixel(P) from image and for each nearest pixel:

    s = M(-1, -1) * P(-1, -1) + M(-1, 0) * P(-1, 0) + ... + M(1, 1) * P(1, 1)  
    
  3. An then division it total value of matrix:

    P'(i, j) = s / M(-1, -1) + M(-1, 0) + ... + M(1, 1)
    

That's all that my program do. I leave extreme pixels not changed.

My program:

for(int i = 1; i < height - 1; i++){  
    for(int j = 1; j < width - 1; j++){  
        int sum = 0, l = 0;
        for(int m = -1; m <= 1; m++){  
            for(int n = -1; n <= 1; n++){  
                try{  
                    System.out.print(l + " ");  
                    sum += mask3[l++] * Byte.toUnsignedInt((byte) source[(i + m) * height + j + n]);  
                } catch(ArrayIndexOutOfBoundsException e){  
                    int ii = (i + m) * height, jj = j + n;  
                    System.out.println("Pixels[" + ii + "][" + jj + "]    " + i + ", " + j);  
                    System.exit(0);  
                }  
            }  
            System.out.println();  
        }  
        System.out.println();  
        output[i * width + j] = sum / maskSum[0];  
    }  
}

I get source from a BufferedImage like this:

int[] source = image.getRGB(0, 0, width, height, null, 0, width);

So for this image: Picture before

Result is this: Picture after

Can you describe me, what is wrong with my program?

fabian
  • 80,457
  • 12
  • 86
  • 114
Wiszen
  • 77
  • 1
  • 4
  • 12
  • 2
    What type is your `source` and `output`, can we see a declaration for them? Because you are making a conversion to byte in `Byte.toUnsignedInt((byte) source[(i + m) * height + j + n])` and I can't believe your color image will fit a pixel into a single byte. Any truncation will let you with a value of max 255, which - if converted to a RGB color space with three bytes/sample - would explain why your result is blue (i.e. missing the R and G components). – Adrian Colomitchi Sep 25 '16 at 08:30
  • `int[] source = image.getRGB(0, 0, width, height, null, 0, width);` `output` is a copy of `source`, so I need to filter 3 times? For R and G? How can I do it? – Wiszen Sep 25 '16 at 11:04
  • 1
    The most rebust way to get individual r, g and b values is to use a (ColorModel)[https://docs.oracle.com/javase/7/docs/api/java/awt/image/ColorModel.html] If you know how the data is stored in then you can extract the 4 byte from the int value for the pixel. r = rgbVal &0xff, g = (rgbVal >> 8) &0xff etc. – Salix alba Sep 25 '16 at 11:38
  • "The most rebust way to get individual r, g and b values is to use a (ColorModel)": and the most unneeded – gpasch Sep 25 '16 at 11:45
  • 1
    @Wiszen "so I need to filter 3 times?" yes. " How can I do it?" A simplified way that will work if you use `image.getRGB(...)` can go through java,awt.Color.[getRed()](https://docs.oracle.com/javase/7/docs/api/java/awt/Color.html#getRed())/[getGreen()](https://docs.oracle.com/javase/7/docs/api/java/awt/Color.html#getGreen())/[getBlue()](https://docs.oracle.com/javase/7/docs/api/java/awt/Color.html#getBlue()) after creating a [Color(int rgb)](https://docs.oracle.com/javase/7/docs/api/java/awt/Color.html#Color(int)). But it's better to go through ColorModel as @Salix suggested – Adrian Colomitchi Sep 25 '16 at 11:55
  • @Wiszen [getting the components of an image](http://stackoverflow.com/questions/2615522/java-bufferedimage-getting-red-green-and-blue-individually) – Adrian Colomitchi Sep 25 '16 at 12:07

1 Answers1

1

First of all, your formula for calculating the index in the source array is wrong. The image data is stored in the array one pixel row after the other. Therefore the index given x and y is calculated like this:

index = x + y * width

Furthermore the color channels are stored in different bits of the int cannot simply do the calculations with the whole int, since this allows channels to influence other channels.

The following solution should work (even though it just leaves the pixels at the bounds transparent):

public static BufferedImage blur(BufferedImage image, int[] filter, int filterWidth) {
    if (filter.length % filterWidth != 0) {
        throw new IllegalArgumentException("filter contains a incomplete row");
    }

    final int width = image.getWidth();
    final int height = image.getHeight();
    final int sum = IntStream.of(filter).sum();

    int[] input = image.getRGB(0, 0, width, height, null, 0, width);

    int[] output = new int[input.length];

    final int pixelIndexOffset = width - filterWidth;
    final int centerOffsetX = filterWidth / 2;
    final int centerOffsetY = filter.length / filterWidth / 2;

    // apply filter
    for (int h = height - filter.length / filterWidth + 1, w = width - filterWidth + 1, y = 0; y < h; y++) {
        for (int x = 0; x < w; x++) {
            int r = 0;
            int g = 0;
            int b = 0;
            for (int filterIndex = 0, pixelIndex = y * width + x;
                    filterIndex < filter.length;
                    pixelIndex += pixelIndexOffset) {
                for (int fx = 0; fx < filterWidth; fx++, pixelIndex++, filterIndex++) {
                    int col = input[pixelIndex];
                    int factor = filter[filterIndex];

                    // sum up color channels seperately
                    r += ((col >>> 16) & 0xFF) * factor;
                    g += ((col >>> 8) & 0xFF) * factor;
                    b += (col & 0xFF) * factor;
                }
            }
            r /= sum;
            g /= sum;
            b /= sum;
            // combine channels with full opacity
            output[x + centerOffsetX + (y + centerOffsetY) * width] = (r << 16) | (g << 8) | b | 0xFF000000;
        }
    }

    BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
    result.setRGB(0, 0, width, height, output, 0, width);
    return result;
}
int[] filter = {1, 2, 1, 2, 4, 2, 1, 2, 1};
int filterWidth = 3;
BufferedImage blurred = blur(img, filter, filterWidth);
fabian
  • 80,457
  • 12
  • 86
  • 114
  • Optionally you can fill the transparent pixels at the edges unblurred like this (this is for the left edge; other sides are likewise): `for (int x = 0; x < filterWidth / 2; x++) {for (int y = 0; y < height; y++) {int pixelIndex = y * width + x;output[pixelIndex] = input[pixelIndex];}}`. Just place it right before the start of the loop `for (int y = 0; y < h; y++) {` and put variables h and w into separate lines before: `int h = height - filter.length / filterWidth + 1; int w = width - filterWidth + 1;`. –  Jun 11 '21 at 14:27