Вопрос

I was trying to get some stats about the compression performance of java ImageIO compression with PNG files, and I was bitten by this crazy behaviour.

I have a bank of PNG (say) 150 test images, and I fed with them this simple code:

for (File ori : files) {
   BufferedImage img = ImageIO.read(ori);
   File dest = new File("C:/temp/x.png"); // whatever
   OutputStream nos = new FileOutputStream(dest)
   ImageIO.write(img, "PNG", nos);
   nos.close();
   long size = dest.length();
   // report size
}

(I've stripped down the exception handling and some unimportant code - also, in my real implementation I don't write to a File but to a dummy outputstream that just count bytes - all that has no influence). This is call from a console Java program, nothing much more than the a main() that calls that. Running from Eclipse, JRE 1.7.0_55-b14 (32bits).

My problem is that this gives quite different results in different runs. Actually, it gives two different sets of results. In one of them, let's call it the "good" case, the compressed sizes are quite smaller (average ~ 87%) thant the "bad" case. I get always these precise two set of results, and only these, and never mixed.

When I get one of the two results seems random, I could not find a pattern. If I run this with only one file, instead of with a long list, I only get one result (the bad one). I could reproduce this with only one image, though the "good" case happens more sporadically, see update below.

I've examined some pairs of good/bad images, and they are OK, no strange things, it just seems as if they were encoded by different encoders.

Can anyone explain this?

enter image description here

Update: this is a pair of "good" and "bad" images.

Update2: Ok, I could reproduce this with only one image. Here's the code:

import java.awt.image.BufferedImage;
import java.io.*;
import java.util.Locale;

import javax.imageio.ImageIO;

public class ImageIoTest {

    public static void main(String[] args) throws Exception {
    Locale.setDefault(Locale.US);
    for (int i = 0; i < 3; i++)
        testWith(new File("C:/temp/m0550.png"));
}

private static long testWith(File f) throws Exception {
    File dest = new File(f.getParent(), "new" + f.getName());
    BufferedImage img = ImageIO.read(f);
    OutputStream nos = new FileOutputStream(dest);
    ImageIO.write(img, "PNG", nos);
    nos.close();
    long size = dest.length();
    System.out.printf("%s\t%d\n", dest, size);
    return size;
}

Set you image path appropiately. I use this image (1103429 bytes), though that should not be critical (but at this time I don't trust any "should"). IF I run the above repeteadly, the 4 calls inside the loop return always the same value. But that same value varies from run to run. Sometimes it gives 1099207 sometimes it gives 1498218 (more strangely even, commenting the Locale line sometimes I get the alteration)... Puzzling.


Final update and explanation: @anonymous, in the accepted answer, got it. The issue was having two ImageWriters (I think this is because once I installed JAI). And it seems that, in that situation, Java selects the prefered one based on... God knows what, perhaps at random. Here we get again the deterministic behaviour we all appreciate:

public class ImageIoTest {

    public static void main(String[] args) throws Exception {
        Iterator<ImageWriter> iter = ImageIO.getImageWritersByFormatName("PNG");
        for(;iter.hasNext();) {
            ImageWriter iw = iter.next();
            testWith(new File("C:/temp/m0550.png"),iw);
        }
    }

    private static long testWith(File f, ImageWriter iw) throws Exception {
        File dest = new File(f.getParent(), "new" + f.getName());
        BufferedImage img = ImageIO.read(f);
        OutputStream nos = new FileOutputStream(dest);
        ImageOutputStream ios = ImageIO.createImageOutputStream(nos);
        iw.setOutput(ios);
        iw.write(img);
        ios.close();
        nos.close();
        long size = dest.length();
        System.out.printf("%s\t%d\t%s\n", dest, size,iw.getOriginatingProvider().getPluginClassName());
        return size;
    }

}

Which in my case prints:

C:\temp\newm0550.png    1099207 com.sun.media.imageioimpl.plugins.png.CLibPNGImageWriter
C:\temp\newm0550.png    1498218 com.sun.imageio.plugins.png.PNGImageWriter
Это было полезно?

Решение

I am running Java 1.8.0.

But then I remember I read that javadoc mentioning about using arbitrary ImageWriter. So I wrote the following code and it printed only 1 ImageWriter. Maybe you have multiple ImageWriter in your system and this could explain the different results you get on each run. `

For Java 8

ImageIO.getImageWritersByFormatName("PNG").forEachRemaining(System.out::println);

For pre Java 1.8

for(Iterator<ImageWriter> iter=ImageIO.getImageWritersByFormatName("PNG");iter.hasNext();) {
    System.out.println(iter.next());
}

Try and see how many ImageWriters you get. If it only list one ImageWriter for you, I am afraid I'm out of ideas.

Just a thought. Let's just say you have multiple ImageWriter, each call to ImageIO.write should give you a different result because an arbitrary ImageWriter is chosen. Unless it only does that on the first call and subsequent calls just re-use it rather than trying to find another "random" ImageWriter to use. Then your result could be explained.

Другие советы

Cannot reproduce. I ran your code 100 times with a set of images varying from 1k to 2.5M, and then 1000 times with your image, and always got the same results. My counting output stream:

class CountingOutputStream extends OutputStream
{
    int  size;

    public int  size()
    {
        return size;
    }

    @Override
    public void write(int b) throws IOException
    {
        size++;
    }

    @Override
    public void write(byte[] b) throws IOException
    {
        size += b.length;
    }

    @Override
    public void write(byte[] b, int offset, int length) throws IOException
    {
        size += length;
    }
}

Your counting may vary ;-) I don't see what the Locale has to do with it. There are mysterious references to it localising compression settings but I don't know what that would mean.

java -version:

java version "1.7.0"
Java(TM) SE Runtime Environment (build 1.7.0-b147)
Java HotSpot(TM) Client VM (build 21.0-b17, mixed mode, sharing)
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top