Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why doesn't ImageIO read a BMP file until it is re-saved in MS Paint?

Tags:

java

image

bmp

I have a bitmap file, test3.bmp, which I can view and edit with every image viewer I have tested with.

That said, I cannot read it into my Java application. If I edit the BMP in MS Paint, save it, undo the change, and save it (test3_resaved.bmp), I have the same image, but with a different file size. The different file sizes do not concern me... what does, is that my application can read the re-saved file.

Could anyone enlighten me on why one image works with my code but the other does not?

Images files:

  • test3.bmp

  • test3_resaved.bmp

Here is a minimal test application:

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

}
like image 748
Thomas Eding Avatar asked Nov 22 '11 00:11

Thomas Eding


1 Answers

(expanding on my comments)

The problem boils down to this: people typically believe that the "format" given by the following command:

ImageIO.getReaderFileSuffixes();

are supported by Java.

But that's not how it should be read/understood because that is simply not how it works.

Wrong: "ImageIO can read any file encoded with one of these format"

Correct: "ImageIO cannot read image encoded with a format that is not one of these format"

But now what does that say about formats appearing in that list? Well... It gets tricky.

For example that list typically returns both "PNG" and "BMP" (and other formats). But there's not "one" PNG nor "one" BMP. I can come tomorrow with a "valid" PNG (sub)format that would be perfectly fine but that no single PNG decoder out there would decode (it would have to be validated and accepted: but once it would be accepted, it would "break" all the existing PNG decoders out there). Luckily, for the PNG pictures the problem ain't too bad.

The BMP format is very complicated. You can have compression or not (which may explain the varying file size you've seen). You can have various headers (of differing length, which may also explain the varying file sized you've seen). Heck, BMP is actually so complex that I think that you can embed PNG encoded pixels inside a BMP "shell".

There are basically two problematic types of BMP files:

  • BMP variants that appeared after the Java decoder was created
  • BMP variants that are obscure enough so that the Java ImageIO implementors didn't consider it worthy of support

The "error" consists in thinking that there's one PNG or one BMP format. Both formats (and other image formats too) are actually "extensible". An everytime a new variant comes out it has the potential to break any decoder out there.

So what's happening in your case is this:

  1. you're reading your original BMP file from MS Paint and MS Paint is able to read that file because it happens to be a BMP format that MS Paint understands.

  2. that same BMP format is alien to the Java version you're using (there's hope that it will be supported in another Java version but I wouldn't count on it).

  3. when you re-save that file from MS Paint, you're saving in a BMP format that is definitely not the same as the original format (the varying file size being quite a tell)

  4. that other format happens to be supported by your version of Java.

Now to actually solve your problem: in my experience image libraries like ImageMagick are able to read much much more pictures than the default Java ImageIO API so I'd give a look at either other image libraries or wrappers around ImageMagick.

These libraries are also typically updated to support newer variants and newer formats much faster than Java is. For example the amazing WebP format from Google (up to 28% to 34% better than PNG on lossless+translucent images) is already supported by quite some image manipulation libraries but I'm not holding my breath when it comes to do a ImageIO.read( someWebPpicture)...

Another option would be to use PNG: even though theoretically PNG can be extended you're less likely to find "non supported" PNGs in the wild. With BMPs it's all too common.

like image 131
TacticalCoder Avatar answered Sep 19 '22 15:09

TacticalCoder