我从 ICO 文件中读取图像数据的方式有什么问题?

What's wrong with the way I read image data from ICO files?

我目前正在尝试编写自己的 ICO 文件实现 reader。

是的,我知道可以使用现有的库,但这不是一个选项。

基本上,我让我的 reader 一直工作到图像数据反序列化点。图标目录条目似乎已正确加载,但我尝试读取的 .ico 文件中的图像数据“不起作用”。

首先,这里是与这个问题相关的文件:

无法正常工作的问题方法是:

private static BufferedImage readImage(InputStream stream, IconDirEntry entry) throws IOException {
    System.out.println("deserializing "+entry);
    byte[] bytes = new byte[entry.data];
    stream.read(bytes);
    if (!isPNG(bytes)) {
        byte[] bmp = makeBitmapFileHeader(bytes);
        bytes = PrimArrays.concat(bmp, bytes);
    }
    return ImageIO.read(new ByteArrayInputStream(bytes));
}

基本上,我的方法是检查图像数据字节是否为 PNG,如果不是,则将它们与新的 header.

连接起来

这样我仍然可以使用 ImageIO 读取位图,而不必自己制作位图反序列化器。

当 运行 代码时,图标目录条目似乎已正确加载并打印到控制台中:

IconDirEntry{dims=16x16, palette=0, planes=0, bpPixel=32, data=1128, offset=86}
IconDirEntry{dims=32x32, palette=0, planes=0, bpPixel=32, data=4264, offset=1214}
IconDirEntry{dims=48x48, palette=0, planes=0, bpPixel=32, data=9640, offset=5478}
IconDirEntry{dims=64x64, palette=0, planes=0, bpPixel=32, data=16936, offset=15118}
IconDirEntry{dims=128x128, palette=0, planes=0, bpPixel=32, data=67624, offset=32054}

通过各种调试,我已经验证 header 确实有 14 个字节的长度,图像数据数组的第一个字节看起来像 BMP 信息块等。

我还验证了在加载图标目录条目中指定的所有字节数时恰好达到了 EOF。

此外,第一个 IconDirEntry 中的偏移量似乎是正确的,因为 ico header 的大小为 6 个字节,而每个 5 ICONDIRENTRY块的大小为 16 字节。 (6 + 5*16 = 86)

net.grian 包中的所有内容都已经过测试并证明可以工作,错误不可能在那里。

尽管看起来一切正常,但在加载第一张图片时出现以下异常:

java.io.EOFException
    at javax.imageio.stream.ImageInputStreamImpl.readFully(ImageInputStreamImpl.java:353)
    at javax.imageio.stream.ImageInputStreamImpl.readFully(ImageInputStreamImpl.java:405)
    at com.sun.imageio.plugins.bmp.BMPImageReader.read32Bit(BMPImageReader.java:1353)
    at com.sun.imageio.plugins.bmp.BMPImageReader.read(BMPImageReader.java:890)
    at javax.imageio.ImageIO.read(ImageIO.java:1448)
    at javax.imageio.ImageIO.read(ImageIO.java:1352)
    at me.headaxe.imtu.io.DeserializerICO.readImage(DeserializerICO.java:103)
    at me.headaxe.imtu.io.DeserializerICO.fromStream(DeserializerICO.java:54)
    at me.headaxe.imtu.io.DeserializerICO.fromStream(DeserializerICO.java:27)
    at me.headaxe.imtu.io.DeserializerICO.fromStream(DeserializerICO.java:18)

我真正需要的只是一个提示,说明为什么尽管我的其余代码工作完美,但图像数据仍无法正确加载。

(由“人工”bmp header + ico文件中的图像数据组成的原始字节供上面下载)

更新:

当我在 ico 文件中加载第一个 bmp 时读取的字节数正好比必要的多 960 个字节,我不再获得 EOFException,而是加载了第一个位图,如下所示: 由于该偏移量,第二个位图显然无法使用 IIOException 加载,因为它格式错误。

更新:

直接对比16x ico和畸形bmp:

更新:

在尝试将它们反序列化为位图之前打印所有字节数组会产生以下结果: 这表明缓冲的字节数是正确的,因为它们都以标志性的 40 开头,这是 BITMAPINFOHEADER 结构的长度。

更新:

这次使用了不同的 ->ico 转换器,效果保持不变,因为缓冲超过 data 字段指定的内容会生成实际的 bmp 文件: 字节为:[40, 0, 0, 0, 32, 0, 0, 0, 64, 0, 0, 0, 1, 0, 32, 0, 0, 0, 0, 0, 0, 16, 0, 0, 35, 46, 0, 0, 35, 46, 0 ... 由于 32 应该是图像宽度而 64 应该是图像高度,我很困惑,因为 ico 是 32x32 一个。

在第二个测试中,.ico 文件有 4286 字节大。 第一个也是唯一一个 ICONDIRENTRY 的偏移量为 22,数据为 4264

尽管完全正确并且分配了正确的字节数,但读取 bmp 失败。

    private static BufferedImage readImage(InputStream stream, IconDirEntry entry) throws IOException {
        byte[] bytes = new byte[entry.data];
        stream.read(bytes);
        if (!isPNG(bytes)) {
            setLittleInt(bytes, 8, entry.height);
            byte[] bmp = makeBitmapFileHeader(bytes);
            bytes = PrimArrays.concat(bmp, bytes);
        }
        if (DEBUG_FILE.exists() && entry.width == 32)
            new SerializerByteArray().toFile(bytes, DEBUG_FILE);
        return ImageIO.read(new ByteArrayInputStream(bytes));
    }

让它工作所需的只是第 5 行。

显然,您不能指望存储在 .ico 文件中的位图不会被损坏,因此手动更正字节数组可以修复 "problem".

我无话可说,我完全目瞪口呆。 好吧,如果有人能解释为什么这样的事情可能会解决它,请发表评论。

如果有人想知道,这是 setLittleInt:

    private static void setLittleInt(byte[] bytes, int index, int value) {
        byte[] ins = IOMath.toBytes(value);
        bytes[index+3] = ins[0];
        bytes[index+2] = ins[1];
        bytes[index+1] = ins[2];
        bytes[index] = ins[3];
    }