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

时间:2017-04-22 21:52:09

标签: java file bitmap io ico

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

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

基本上,我让读者进行操作直到图像数据反序列化。图标目录条目似乎已正确加载,但我尝试阅读的.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,如果不是,则将它们与新标题连接。

这样我仍然可以使用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}

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

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

此外,第一个IconDirEntry中的偏移量似乎是正确的,因为ico标头的大小为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标题组成的原始字节+来自ico文件的图像数据可供上载下载)

更新

当我在ico文件中加载第一个bmp时,我确切地读了960多个字节,我不再得到EOFException,而是第一个位图被加载,看起来像这样: enter image description here 由于该偏移,第二个位图显然无法加载IIOException,因为它格式不正确。

更新

16x ico与格式错误的bmp之间的直接比较: enter image description here

更新

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

更新

这次使用了不同的->ico转换器,效果保持不变,因为缓冲超过data字段指定生成实际的bmp文件: enter image description here 字节数为:[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失败。

1 个答案:

答案 0 :(得分:0)

    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文件中的位图,不会损坏垃圾,因此手动更正字节数组可以解决问题"。

我对此没有任何言论,我完全是傻瓜。 好吧,如果有人解释为什么这样的事情可能会解决它,请发表评论。

如果有人想知道,这是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];
    }