4

我试图将 aBufferedImagebyte[]32 位 RGBA 转换为 24 位 RGB。根据这个答案,从图像中获取的最快方法byte[]是:

byte[] pixels = ((DataBufferByte) bufferedImage.getRaster().getDataBuffer()).getData();

所以我迭代所有字节,假设它们的顺序是 RGBA,并且对于每 4 个字节,我将前 3 个写入输出 byte[](即忽略 alpha 值)。

当从 Eclipse 运行并且字节被正确转换时,这工作正常。但是,当我从命令行运行相同的程序时,会以相反的字节顺序返回相同的字节!

我用于测试的测试图像是 5x5 黑色图像,其中只有左上角的 RGBA 颜色不同[aa cc ee ff]测试图像 5x5

为了方便起见,还有一个放大版本:

放大的测试图像

我的文件夹结构是:

- src/  
  - test.png
  - test/
      - TestBufferedImage.java  

SSCCE如下:

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]) + "]"
        );
  }

}

从 Eclipse 运行时的输出(版本: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

从命令行(Windows Vista)运行时的输出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

那么有没有人遇到过类似的问题和/或可以解释实际发生了什么?为什么从 Eclipse 内部运行时字节顺序不同?

4

1 回答 1

3

在回答我自己的问题之前,我非常非常非常感谢@Joni 和@haraldK,他们为我指明了正确的方向。我对 , 等内部的知识BufferedImage不是ColorModel很好SampleModel,所以他们帮助了我。

所以这就是发生的事情:

首先,不同的行为是由不同的 JRE 引起的。用于打印 java 版本的日志语句显示 Eclipse1.6.0_16-b01在命令行打印时打印1.6.0_31-b05。显然,图像加载的实现(这将是PNGImageReader类)在版本之间发生了变化,我怀疑它在这个变化期间发生了变化

尽管如此,尽管两个版本使用相同ColorModelSampleModel所以我无法理解这种变化(对我来说这似乎是一个真正的密码破解者),所以我进行了进一步调查。

两个版本之间的重要变化在于确定可用于创建新图像的可用兼容格式PNGImageReader的方法:Iterator<ImageTypeSpecifier> getImageTypes()

版本 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;

版本 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;

所以在较新的版本中,新ImageTypeSpecifier的 s 对 PNG 加载器说内部表示的兼容图像是BufferedImage.TYPE_4BYTE_ABGR并且因为它是列表中的第一个类型,所以加载器继续使用它。这就是颜色通道(以及字节)的波段被反转的原因。

意识到上述情况,我突然意识到这不是我想的错误,也不是代码破坏者。原因是因为我是byte[] pixels = ((DataBufferByte) bufferedImage.getRaster().getDataBuffer()).getData();用来获取字节的,所以我实际上是在破解SampleModel图像的内部数据结构(即忽略 )。合同中没有任何内容可以ImageReader保证字节的顺序。如果需要,可以随意更改其内部数据结构(这就是封装的重点,对吧?)。如果您想要除默认行为之外的任何其他内容,则正确配置的方法ImageReader是获取其默认值ImageReadParam并对其进行配置。然后使用reader.read(imageIndex, param);

因为我实际上希望读者返回图像字节的特定格式,所以我应该这样做:

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); 

现在两个版本都将以相同的 RGBA 形式返回字节的顺序,即不同颜色的输出将在两种情况下打印:

...
i=0 Converting [aa, cc, ee, ff] --> [aa, cc, ee] 
...
于 2013-08-08T10:09:07.487 回答