I had a similar problem with blurring a 16 bit BufferedImage
and ConvolveOp
, but in my case a grayscale picture (BufferedImage.TYPE_BYTE_GRAY
). The image I needed to process is a heightmap.
I used the following code:
private static BufferedImage blur(BufferedImage img) {
float[] matrix3 = {
1f/9, 1f/9, 1f/9,
1f/9, 1f/9, 1f/9,
1f/9, 1f/9, 1f/9,
};
Kernel kernel = new Kernel(3, 3, matrix3);
BufferedImageOp op = new ConvolveOp(kernel);
return op.filter(img, null);
}
This results in the following artifacts (images cropped due to their large size):
Original image:

Blurred image:

Now, instead of using ConvolveOp
, I can achieve the same effect with blurring all rows and all cols individually in only one dimension (cf. here) with a factor of 1/3 instead of 1/9 in the matrix, because 1/3 * 1/3 = 1/9. The following code does this, but leads to the same artifacts as the ConvolveOp
used above:
private static BufferedImage blur(BufferedImage img) {
for (int x=0; x<img.getWidth(); x++) {
for (int y=0; y<img.getHeight(); y++) {
int y0 = Math.max(y-1, 0);
int y1 = y;
int y2 = Math.min(y+2, img.getHeight()-1);
short s0 = ((short[]) img.getRaster().getDataElements(x, y0, null))[0];
short s1 = ((short[]) img.getRaster().getDataElements(x, y1, null))[0];
short s2 = ((short[]) img.getRaster().getDataElements(x, y2, null))[0];
short sNew = (short) ((s0 + s1 + s2) / 3);
img.getRaster().setDataElements(x, y, new short[] {(short)sNew});
}
}
for (int x=0; x<img.getWidth(); x++) {
for (int y=0; y<img.getHeight(); y++) {
int x0 = Math.max(x-1, 0);
int x1 = x;
int x2 = Math.min(x+2, img.getWidth()-1);
short s0 = ((short[]) img.getRaster().getDataElements(x0, y, null))[0];
short s1 = ((short[]) img.getRaster().getDataElements(x1, y, null))[0];
short s2 = ((short[]) img.getRaster().getDataElements(x2, y, null))[0];
short sNew = (short) ((s0 + s1 + s2) / 3);
img.getRaster().setDataElements(x, y, new short[] {(short)sNew});
}
}
return img;
}
Workaround:
With the following code (added lines marked), I can get rid of these artifacts and the blurring is correct:
private static BufferedImage blur(BufferedImage img) {
for (int x=0; x<img.getWidth(); x++) {
for (int y=0; y<img.getHeight(); y++) {
int y0 = Math.max(y-1, 0);
int y1 = y;
int y2 = Math.min(y+2, img.getHeight()-1);
short s0 = ((short[]) img.getRaster().getDataElements(x, y0, null))[0];
short s1 = ((short[]) img.getRaster().getDataElements(x, y1, null))[0];
short s2 = ((short[]) img.getRaster().getDataElements(x, y2, null))[0];
s0 += 32768; // ADDED LINE
s1 += 32768; // ADDED LINE
s2 += 32768; // ADDED LINE
short sNew = (short) ((s0 + s1 + s2) / 3);
sNew -= 32768; // ADDED LINE
img.getRaster().setDataElements(x, y, new short[] {(short)sNew});
}
}
for (int x=0; x<img.getWidth(); x++) {
for (int y=0; y<img.getHeight(); y++) {
int x0 = Math.max(x-1, 0);
int x1 = x;
int x2 = Math.min(x+2, img.getWidth()-1);
short s0 = ((short[]) img.getRaster().getDataElements(x0, y, null))[0];
short s1 = ((short[]) img.getRaster().getDataElements(x1, y, null))[0];
short s2 = ((short[]) img.getRaster().getDataElements(x2, y, null))[0];
s0 += 32768; // ADDED LINE
s1 += 32768; // ADDED LINE
s2 += 32768; // ADDED LINE
short sNew = (short) ((s0 + s1 + s2) / 3);
sNew -= 32768; // ADDED LINE
img.getRaster().setDataElements(x, y, new short[] {(short)sNew});
}
}
return img;
}
Final image:

It seems to me like it has to do something with the Java short
type being signed, while the type of the BufferedImage
is unsigned, but I'm not yet completely sure about that. Maybe this helps solving your problem.