问题
I was trying to convert a BufferedImage
's byte[]
from 32-bit RGBA to 24-bit RGB. According to this answer the fastest way to get the byte[]
from the image is:
byte[] pixels = ((DataBufferByte) bufferedImage.getRaster().getDataBuffer()).getData();
So I iterate over all bytes assuming their order is R G B A and for every 4 bytes, I write the first 3 in an output byte[] (i.e. ignoring the alpha value).
This works fine when run from Eclipse and the bytes are converted correctly. However when I run the same program from the command line the same bytes are returned with the opposite byte order!
The test image I use for my test is a 5x5 black image where only its top-left corner is different having the RGBA color [aa cc ee ff]
:
And a zoomed-in version for conveniency:
My folder structure is:
- src/
- test.png
- test/
- TestBufferedImage.java
The SSCCE is the following:
package test;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
import java.io.IOException;
import java.io.InputStream;
import javax.imageio.ImageIO;
public class TestBufferedImage {
private static void log(String s) {
System.out.println(s);
}
private static String toByteString(byte b) {
// Perform a bitwise AND for convenience while printing.
// Otherwise Integer.toHexString() interprets values as integers and a negative byte 0xFF will be printed as "ffffffff"
return Integer.toHexString(b & 0xFF);
}
/**
* @param args
* @throws IOException
*/
public static void main(String[] args) throws IOException {
InputStream stream = TestBufferedImage.class.getClassLoader().getResourceAsStream("test.png");
BufferedImage image = ImageIO.read(stream);
stream.close();
log("Image loaded succesfully, width=" + image.getWidth() + " height=" + image.getHeight());
log("Converting from 32-bit to 24-bit...");
DataBufferByte buffer = (DataBufferByte)image.getRaster().getDataBuffer();
byte[] input = buffer.getData();
byte[] output = convertTo24Bit(input);
log("Converted total of " + input.length + " bytes to " + output.length + " bytes");
}
private static byte[] convertTo24Bit(byte[] input) {
int dataLength = input.length;
byte[] convertedData = new byte[ dataLength * 3 / 4 ];
for (int i = 0, j = 0; i < dataLength; i+=4, j+=3) {
convertIntByteToByte(input, i, convertedData, j);
}
return convertedData;
}
private static void convertIntByteToByte(byte[] src, int srcIndex, byte[] out, int outIndex) {
byte r = src[srcIndex];
byte g = src[srcIndex+1];
byte b = src[srcIndex+2];
byte a = src[srcIndex+3];
out[outIndex] = r;
out[outIndex+1] = g;
out[outIndex+2] = b;
log("i=" + srcIndex
+ " Converting [" + toByteString(r) + ", " + toByteString(g)
+ ", " + toByteString(b) + ", " + toByteString(a) + "] --> ["
+ toByteString(out[outIndex]) + ", " + toByteString(out[outIndex+1])
+ ", " + toByteString(out[outIndex+2]) + "]"
);
}
}
Output when run from Eclipse (Version: Juno Service Release 2 Build id: 20130225-0426):
Image loaded succesfully, width=5 height=5
Converting from 32-bit to 24-bit...
i=0 Converting [aa, cc, ee, ff] --> [aa, cc, ee] // <-- Bytes have the correct order
i=4 Converting [0, 0, 0, ff] --> [0, 0, 0]
i=8 Converting [0, 0, 0, ff] --> [0, 0, 0]
.....
i=96 Converting [0, 0, 0, ff] --> [0, 0, 0]
Converted total of 100 bytes to 75 bytes
Output when run from command line (Windows Vista) with java test.TestBufferedImage
:
Image loaded succesfully, width=5 height=5
Converting from 32-bit to 24-bit...
i=0 Converting [ff, ee, cc, aa] --> [ff, ee, cc] // <-- Bytes are returned with a different byte order!
i=4 Converting [ff, 0, 0, 0] --> [ff, 0, 0]
i=8 Converting [ff, 0, 0, 0] --> [ff, 0, 0]
.....
i=96 Converting [ff, 0, 0, 0] --> [ff, 0, 0]
Converted total of 100 bytes to 75 bytes
So has anyone encountered a similar issue and/or can explain what is actually going on? Why the byte order is different when running from inside Eclipse?
回答1:
Before answering my own question I would like to really, really, REALLY thank @Joni and @haraldK that pointed me to the right direction. My knowledge for the internals of the BufferedImage
, ColorModel
, SampleModel
and the like is not so great so they have helped me out.
So here is what happened:
First of all the different behaviour was caused by different JREs. A log statement for printing the java version revealed that Eclipse prints 1.6.0_16-b01
while the command line prints 1.6.0_31-b05
. Apparently the implementation of the image loading (this would be the PNGImageReader
class) has changed between versions and I suspect that it did during this change.
Still though both versions use the same ColorModel
and SampleModel
so I couldn't understand this change (it seemed a real code-breaker to me) so I have investigated further.
The important change between the two versions of the PNGImageReader
was in the Iterator<ImageTypeSpecifier> getImageTypes()
method that decides the available compatible formats that can be used to create the new image:
version 1.6.0_16-b01:
...
case PNG_COLOR_RGB_ALPHA:
// Component R, G, B, A (non-premultiplied)
rgb = ColorSpace.getInstance(ColorSpace.CS_sRGB);
bandOffsets = new int[4];
bandOffsets[0] = 0;
bandOffsets[1] = 1;
bandOffsets[2] = 2;
bandOffsets[3] = 3;
l.add(ImageTypeSpecifier.createInterleaved(rgb,
bandOffsets,
dataType,
true,
false));
break;
version 1.6.0_31-b05:
...
case PNG_COLOR_RGB_ALPHA:
if (bitDepth == 8) {
// some standard types of buffered images
// wich can be used as destination
l.add(ImageTypeSpecifier.createFromBufferedImageType(
BufferedImage.TYPE_4BYTE_ABGR));
l.add(ImageTypeSpecifier.createFromBufferedImageType(
BufferedImage.TYPE_INT_ARGB));
}
// Component R, G, B, A (non-premultiplied)
rgb = ColorSpace.getInstance(ColorSpace.CS_sRGB);
bandOffsets = new int[4];
bandOffsets[0] = 0;
bandOffsets[1] = 1;
bandOffsets[2] = 2;
bandOffsets[3] = 3;
l.add(ImageTypeSpecifier.createInterleaved(rgb,
bandOffsets,
dataType,
true,
false));
break;
So in the newer version, the new ImageTypeSpecifier
s say to the PNG loader that a compatible image for the internal representation is the BufferedImage.TYPE_4BYTE_ABGR
and since it is the first type in the list, the loader goes ahead and uses that. This is why the bands for the color channels (and thus the bytes) are reversed.
Realizing the above, it suddenly hit me that this was not a bug nor a code breaker as I thought. The reason is that because I was using byte[] pixels = ((DataBufferByte) bufferedImage.getRaster().getDataBuffer()).getData();
to get the bytes I was actually hacking my way to the internal data structures (i.e. ignoring the SampleModel
) of the image. Nothing in the contract of the ImageReader
guarantees the order of the bytes. It is free to change its internal data stuctures if it wants (this is the point of encapsulation right?). The way to correctly configure an ImageReader
if you desire anything else than the default behaviour is to get its default ImageReadParam
and configure it. Then pass it back to the reader by using reader.read(imageIndex, param);
Since I actually want the reader to return a specific format for the image bytes I should do this:
log("Java version: " + System.getProperty("java.runtime.version"));
// get the reader
ImageReader ir = ImageIO.getImageReadersByFormatName("png").next();
// get the default param
ImageReadParam p = ir.getDefaultReadParam();
p.setDestinationType(
// define the image type to return if supported
ImageTypeSpecifier.createInterleaved(
ColorSpace.getInstance(ColorSpace.CS_sRGB),
new int[] {0, 1, 2, 3}, // <-- the order of the color bands to return so the bytes are in the desired order
DataBuffer.TYPE_BYTE,
true, false)
);
InputStream stream = TestBufferedImage.class.getClassLoader().getResourceAsStream("test.png");
ImageInputStream imageStream = ImageIO.createImageInputStream(stream);
ir.setInput(imageStream);
BufferedImage image = ir.read(0, p);
Now both versions will return the order of the bytes in the same RGBA form i.e. the output for the different color will print in both cases:
...
i=0 Converting [aa, cc, ee, ff] --> [aa, cc, ee]
...
来源:https://stackoverflow.com/questions/18099959/bufferedimage-bytes-have-a-different-byte-order-when-running-from-eclipse-and-t