为啥 ImageIO 不读取 BMP 文件,直到它被重新保存在 MS Paint 中?

Posted

技术标签:

【中文标题】为啥 ImageIO 不读取 BMP 文件,直到它被重新保存在 MS Paint 中?【英文标题】:Why doesn't ImageIO read a BMP file until it is re-saved in MS Paint?为什么 ImageIO 不读取 BMP 文件,直到它被重新保存在 MS Paint 中? 【发布时间】:2011-11-22 00:41:38 【问题描述】:

我有一个位图文件test3.bmp,我可以使用我测试过的每个图像查看器查看和编辑它。

也就是说,我无法将它读入我的 Java 应用程序。如果我在 MS Paint 中编辑 BMP,保存它,撤消更改并保存它 (test3_resaved.bmp),我有相同的图像,但文件大小不同。不同的文件大小与我无关......我的应用程序可以读取重新保存的文件。

谁能告诉我为什么一张图片适用于我的代码而另一张不适用?

图片文件:

test3.bmp

test3_resaved.bmp

这是一个最小的测试应用程序:

package Test;

import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.swing.ImageIcon;
import javax.swing.JFrame;

@SuppressWarnings("serial")
public class Test extends JFrame 
    private ImageIcon imageIcon;

    public Test(String filename) throws IOException 
        super();
        BufferedImage image = javax.imageio.ImageIO.read(new File(filename));
        imageIcon = new ImageIcon(image);
        setVisible(true);
        setDefaultCloseOperation(DISPOSE_ON_CLOSE);
        repaint();
    

    public void paint(Graphics g) 
        Graphics2D g2d = (Graphics2D) g;
        setSize(imageIcon.getIconWidth(), imageIcon.getIconHeight());
        if (imageIcon != null)
            g2d.drawImage(imageIcon.getImage(), 0, 0, this);
    


    /**
     * @param args
     */
    public static void main(String[] args) 
        try 
            if (args.length > 0)
                new Test(args[0]);
            else
                System.out.println("usage - specify image filename on command line");
        
        catch (Throwable t) 
            t.printStackTrace();
        
    


【问题讨论】:

LOT 种不同类型的 BMP 文件。可能您的 MS Paint 可以读取原始格式,但在保存文件时正在使用其他 BMP 标头/编码。我确信 Java 绝对无法读取所有各种 BMP 文件(很少有 BMP 阅读器可以)。恰好Java支持MS Paint输出的那种特定类型的BMP。为什么选择 BMP?如果你想要无损,PNG 是一种选择吗? 这里的问题与“Roman B.”相同... BMP 文件的链接无法正常工作。您可以对两个文件的标题(开头)进行 hexdump 并将结果添加到您的问题中,但无论如何对我来说,问题正是我上面描述的:-/ 【参考方案1】:

(在我的 cmets 上展开)

问题归结为:人们通常认为以下命令给出的“格式”:

ImageIO.getReaderFileSuffixes();

受 Java 支持。

但这不是它应该被阅读/理解的方式,因为这根本不是它的工作方式。

错误:“ImageIO 可以读取使用这些格式之一编码的任何文件”

正确:“ImageIO 无法读取以不是这些格式之一的格式编码的图像”

但是现在关于出现在该列表中的格式说明了什么?嗯...这很棘手。

例如,该列表通常会返回“PNG”和“BMP”(以及其他格式)。但是没有“一个”PNG,也没有“一个”BMP。我明天可以带着一个“有效的”PNG(子)格式来,它会非常好,但那里没有一个 PNG 解码器可以解码(它必须经过验证和接受:但一旦它被接受,它就会“崩溃“所有现有的PNG解码器)。幸运的是,对于 PNG 图片,问题还算不错。

BMP 格式非常复杂。您可以进行压缩或不进行压缩(这可以解释您所看到的不同文件大小)。您可以有各种标题(长度不同,这也可以解释您所看到的不同文件大小)。哎呀,BMP实际上是如此复杂,以至于我认为您可以将PNG编码的像素嵌入到BMP“外壳”中。

基本上有两种有问题的BMP文件类型:

创建 Java 解码器后出现的 BMP 变体 BMP 变体过于晦涩,以至于 Java ImageIO 实现者认为它不值得支持

“错误”在于认为存在一种 PNG 或一种 BMP 格式。这两种格式(以及其他图像格式)实际上都是“可扩展的”。每次出现新变体时,它都有可能破坏任何解码器。

所以你的情况是这样的:

    您正在从 MS Paint 读取原始 BMP 文件,并且 MS Paint 能够读取该文件,因为它恰好是 MS Paint 可以理解的 BMP 格式。

    相同的 BMP 格式与您使用的 Java 版本不同(希望它会在另一个 Java 版本中得到支持,但我不会指望它)。

    当您从 MS Paint 中重新保存该文件时,您保存的 BMP 格式肯定与原始格式相同(不同的文件大小相当告诉)

    您的 Java 版本恰好支持其他格式。

现在要真正解决您的问题:根据我的经验,像 ImageMagick 这样的图像库能够读取比默认 Java ImageIO API 更多的图片,所以我会看看其他图像库或 ImageMagick 的包装器。

这些库通常也会进行更新,以支持比 Java 更快的新变体和新格式。例如,谷歌令人惊叹的 WebP 格式(在无损+半透明图像上比 PNG 好 28% 到 34%)已经被相当多的图像处理库支持,但我并没有屏住呼吸它来做一个 ImageIO.read(someWebPpicture)...

另一种选择是使用 PNG:即使理论上可以扩展 PNG,您也不太可能在野外找到“不支持”的 PNG。对于 BMP,这太常见了。

【讨论】:

我通常忽略提及图像(和大多数声音)格式是“容器格式”,除非出现突出编码之间差异的内容。 +1 为您提供全面的解释。【参考方案2】:

这里有一些示例代码 http://www.java2s.com/Code/Java/2D-Graphics-GUI/ListAllreaderandwriterformatssupportedbyImageIO.htm 将枚举您的 JDK 支持的图像格式。

高级图像工具包http://www.oracle.com/technetwork/java/release-jai-imageio-1-0-01-140367.html 支持BMP,但我知道它也有现在基本JDK 支持的东西。因此,如果两者都支持,那么 JAI 支持可能更全面。不过,这似乎不太可能,因为它没有多大意义。 OTOH,它太阳。

如果你使用的是 JDK 6,你肯定可以做 PNG(更便携),你能转换你的图像吗? IIRC MS Paint 将保存一个 png。

【讨论】:

声称由 ImageIO 阅读器支持的图像“格式”具有高度误导性,正如我在冗长的回答中所解释的那样:) 如果真的支持 BMP,则OP 可以使用 ImageIO 打开他的图像,就像他可以从 MS Paint 打开它一样。事实并非如此。【参考方案3】:

我使用自己的 Java BMP 解码器测试了这两个图像。它还转储图像的一些信息。我发现原来的是一个 32 位的 bmp,而重新保存的是一个 24 位的 bmp。所以我假设 imageio bmp 阅读器无法正确处理 32 位 bmp。

更新:为了确认我很久以前的猜测,我再次测试了图像,但仍然存在问题。问题是没有图像显示,原因是 Java ImageIO 认为图像是完全透明的。以下是来自 Java ImageIO 创建的 BufferedImage 的转储:

 DirectColorModel: rmask=ff0000 gmask=ff00 bmask=ff amask=ff000000
 IntegerInterleavedRaster: width = 494 height = 516 #Bands = 4 xOff = 0 yOff = 0
 dataOffset[0] 0
 java.awt.image.SinglePixelPackedSampleModel@80809ee

我们可以看到这里有 4 个波段代表 RGBA,因为 Java ImageIO 对其进行了解释。事实是第四个波段或第四个字节不适用于 32 位 Windows BMP 图像的 alpha 通道。它只是垃圾或使其双字对齐。

一个 Windows 3.x BMP 解码器和更多来自这里https://github.com/dragon66/icafe

【讨论】:

以上是关于为啥 ImageIO 不读取 BMP 文件,直到它被重新保存在 MS Paint 中?的主要内容,如果未能解决你的问题,请参考以下文章

ImageIO.write bmp 不起作用

ImageIO.read() 返回 null

在 JPG 图像中隐藏消息

读取 BMP 标头,打包。读取不正确的值

无法使用 ImageIO.read(文件文件)读取 JPEG 图像

在 Python 中读取 bmp 文件