Reputation: 75926
(This is a followup to a previous question)
The standard recommended way of transforming a BufferedImage
from one type to another, or to make a copy, is to use getGraphics().drawImage()
(example). To my surprise, I've found that this procedure does not leave the pixel value unaltered, even even when both the source and target images are both of the same type! The issue shows up when there is some transparency.
Example with a single pixel ARGB image:
public static void imagesTestBiIssue() throws IOException {
//it also happens with TYPE_INT_ARGB
BufferedImage bi1 = new BufferedImage(1, 1, BufferedImage.TYPE_4BYTE_ABGR);
// rather arbitrary. low values of alpha tend to give more difference
int argb = 0x11663322;
bi1.setRGB(0, 0, argb);
int p1 = bi1.getRGB(0, 0);
BufferedImage bi2 = new BufferedImage(bi1.getWidth(), bi1.getHeight(),
bi1.getType());
bi2.getGraphics().drawImage(bi1, 0, 0, null);
int p2 = bi2.getRGB(0, 0);
System.out.printf("im1: %08x %s ", p1, formatARGB(p1));
System.out.printf("im2: %08x %s %s\n", p2,
formatARGB(p2), (p1 == p2 ? "" : "DIF"));
}
public static String formatARGB(int v) {
return String.format("(%d,%d,%d,%d)",
(v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF);
}
This gives: im1: 11663322 (17,102,51,34) im2: 11692d1e (17,105,45,30) DIF
There seems to be some colour conversion somewhere, I cannot imagine why, given that the target and source are of the same type. Is this expected (or acceptable) behaviour?
Upvotes: 3
Views: 1438
Reputation: 27084
I finally got it. Try setting graphics.setComposite(AlphaComposite.Src)
. In my own library code I do that, but I have never given it much thought...
Because the default composite is AlphaComposite.SrcOver
, you're actually composing semi-transparent pixels onto completely transparent pixels, both with non-premultiplied alpha, so there is a difference here. Using AlphaComposite.Src
you basically say that only the source matters.
Also note that lower alpha values means more transparent. This means that for low alpha, the RGB values has less significance, as they are multiplied by alpha when composing (ie. 17/255
for your example image), thus the diff will not make a difference when composed onto an opaque background.
So I'd say: Somewhat unexpected? Yes. Acceptable? Probably, yes. :-)
Here's an updated version of your code, using AlphaComposite.Src
, and the output with no diff:
public static void main(String[] args) {
//it also happens with TYPE_INT_ARGB
BufferedImage bi1 = new BufferedImage(1, 1, BufferedImage.TYPE_4BYTE_ABGR);
// rather arbitrary. low values of alpha tend to give more difference
int argb = 0x11663322;
bi1.setRGB(0, 0, argb);
int p1 = bi1.getRGB(0, 0);
BufferedImage bi2 = new BufferedImage(bi1.getWidth(), bi1.getHeight(),
bi1.getType());
Graphics2D graphics = bi2.createGraphics();
try {
graphics.setComposite(AlphaComposite.Src);
graphics.drawImage(bi1, 0, 0, null);
}
finally {
graphics.dispose();
}
int p2 = bi2.getRGB(0, 0);
System.out.printf("im1: %08x %s ", p1, formatARGB(p1));
System.out.printf("im2: %08x %s %s\n", p2,
formatARGB(p2), (p1 == p2 ? "" : "DIF"));
}
public static String formatARGB(int v) {
return String.format("(%d,%d,%d,%d)",
(v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF);
}
Output:
im1: 11663322 (17,102,51,34) im2: 11663322 (17,102,51,34)
Upvotes: 2
Reputation: 75926
I think I've found it.
I've checked that the above (A,R,G,B) values are "real" (they are stored in the DataBuffer as such). But it seems that (at least in my version) drawImage()
uses/assumes alphaPremultiplied values. So, in the image pipe, the original values are pre-multiplied (with 8 bits quantization) on drawing, and then, when stored in the target BufferedImage, the inverse transformation is done. The equation are, for each RGB channel, approximately:
r <-- original red value
r1 = round( r * a / 255.0 ) <-- alpha multiplied
r2 = round( r1 * 255.0 /a ) <-- restored
(This is mere empirical -I did't dig into the sources- and there are some rounding issues, but the essence is there).
This transformation is lossy, of course (more so for low alpha values; in particular, it clears all values if alpha is zero), but it is nonetheless harmless... if we assume that we are only interested in displaying or keeping the post-multiplied values. For some image processing scenarios, this assumption can be wrong.
Upvotes: 1