我试图获取有关使用PNG文件进行Java ImageIO压缩的压缩性能的统计数据,但我被这种疯狂的行为所困扰。

我有一堆PNG(例如)150张测试图像,并用以下简单代码喂饱了它们:

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
}

(我删除了异常处理和一些不重要的代码-同样,在我的实际实现中,我不会写入文件,而是写入仅计数字节的虚拟输出流-所有这些都没有影响)。这是从控制台Java程序调用的,无非就是调用它的main()。从Eclipse运行,JRE 1.7.0_55-b14(32位)。

我的问题是,这在不同的运行中给出了完全不同的结果。实际上,它给出了两组不同的结果。在其中一种情况下,我们称其为“好”情况,压缩后的大小比“坏”情况小得多(平均约87%)。我总是得到这两组精确的结果,只有这些结果,并且从未混淆。

当我得到两个结果之一似乎是随机的时,我找不到模式。如果仅使用一个文件而不是一长串的文件运行此文件,则只会得到一个结果(不好的结果)。我可以仅用一张图像来重现此图像,尽管“好”情况偶尔发生,请参见下面的更新。

我已经检查了几对好/坏图像,它们还可以,没什么奇怪的,看起来好像它们是由不同的编码器编码的。

谁能解释一下?

更新:这是一对“good”和“bad”图像。

Update2:好的,我只可以复制一张图像。这是代码:
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;
}

适当设置图像路径。我使用this image(1103429字节),尽管这并不重要(但目前我不信任任何“应该”)。如果我反复运行上述命令,则循环内的4个调用始终返回相同的值。 但是相同的值因运行而异。有时它给出1099207,有时它给出1498218(甚至更奇怪的是,有时在注释区域设置行时也会得到更改)...令人费解。

最终更新和解释:@anonymous,在接受的答案中,知道了。问题是有两个ImageWriter(我认为这是因为一旦我安装了JAI)。在这种情况下,Java似乎是根据……选择首选的。上帝知道,也许是随机的。
在这里,我们再次得到大家都赞赏的确定性行为:
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;
    }

}

在我的情况下打印:
C:\temp\newm0550.png    1099207 com.sun.media.imageioimpl.plugins.png.CLibPNGImageWriter
C:\temp\newm0550.png    1498218 com.sun.imageio.plugins.png.PNGImageWriter

最佳答案

我正在运行Java 1.8.0。

但是后来我记得我读过那篇有关使用arbitrary ImageWriter的javadoc。所以我写了下面的代码,它只打印了1个ImageWriter。也许您的系统中有多个ImageWriter,这可以解释每次运行时获得的不同结果。 `

对于Java 8

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

对于Java 1.8之前的版本
for(Iterator<ImageWriter> iter=ImageIO.getImageWritersByFormatName("PNG");iter.hasNext();) {
    System.out.println(iter.next());
}

尝试看看您获得了多少ImageWriter。如果它仅为您列出一个ImageWriter,恐怕我就没主意了。

只是一个想法。假设您有多个ImageWriter,每次调用ImageIO.write都会给您带来不同的结果,因为选择了任意ImageWriter。除非它仅在第一次调用和随后的调用中执行此操作,否则仅重用它而不是尝试查找要使用的另一个“随机” ImageWriter。然后可以解释您的结果。

10-02 05:19