使用JAI(Java高级图像)压缩PNG图片

6

我正在使用javax.imageio API和JAI来压缩不同类型的图像。对于使用JPEGImageWriter进行JPEG压缩和GIFImageWriter进行GIF压缩的情况,它都正常工作。但是使用PNGImageWriter进行PNG压缩时会抛出异常,如未设置压缩类型或“没有有效压缩”等。因此,我使用了下面这个ImageWriter来进行PNG压缩。它可以工作,但是图像质量太差。

有人能建议如何使用PNGImageWriter进行PNG压缩以及包含它的JAR文件吗?

File input = new File("test.png");

InputStream is = new FileInputStream(input);
BufferedImage image = ImageIO.read(is);

File compressedImageFile = new File(input.getName());

OutputStream os =new FileOutputStream(compressedImageFile);

Iterator<ImageWriter>writers = 
        ImageIO.getImageWritersByFormatName("jpg"); // here "png" does not work
ImageWriter writer = (ImageWriter) writers.next();

ImageOutputStream ios = ImageIO.createImageOutputStream(os);
writer.setOutput(ios);

ImageWriteParam param = writer.getDefaultWriteParam();
param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
param.setCompressionQuality(0.5f);

writer.write(null, new IIOImage(image, null, null), param);
2个回答

12
更新:自Java 9起,JDK捆绑的默认com.sun.imageio.plugins.png.PNGImageWriter现在支持设置和指定压缩级别。
如果您仍在使用Java 8或更早版本,也有一个后移版本可用。 本答案的其余部分假设您正在使用没有后移版本的旧版Java(不要这样做!)。
似乎随JRE捆绑的默认com.sun.imageio.plugins.png.PNGImageWriter不支持设置压缩。这有点令人惊讶,因为该格式显然支持压缩。然而,PNGImageWriter始终写入压缩的内容。
从源代码可以看出,它使用的是:
Deflater def = new Deflater(Deflater.BEST_COMPRESSION);

这将给你一个好的,但是慢速压缩。对你来说可能足够好,但对于某些情况来说,使用更快的压缩和更大的文件可能会更好。
为了修复你的代码,使其适用于任何格式的名称,请更改以下行:
ImageWriteParam param = writer.getDefaultWriteParam();
param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
param.setCompressionQuality(0.5f);

给:
ImageWriteParam param = writer.getDefaultWriteParam();

if (param.canWriteCompressed()) { 
    // NOTE: Any method named [set|get]Compression.* throws UnsupportedOperationException if false
    param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
    param.setCompressionQuality(0.5f);
}

它仍然会写入压缩的PNG文件。

如果您需要更多对PNG压缩的控制,比如设置压缩或使用的滤镜,您需要找到一个支持它的ImageWriter。正如您提到的JAI,我认为jai-imageio.jarjai-imageio-tools.jar中的CLibPNGImageWriter支持设置压缩。您只需要浏览ImageWriter的迭代器,看看您是否已经安装了它:

Iterator<ImageWriter>writers = ImageIO.getImageWritersByFormatName("png");
ImageWriter writer = null;
    
while (writers.hasNext()) {
    ImageWriter candidate = writers.next();
        
    if (candidate.getClass().getSimpleName().equals("CLibPNGImageWriter")) {
        writer = candidate; // This is the one we want
        break;
    }
    else if (writer == null) {
        writer = candidate; // Any writer is better than no writer ;-)
    }
}

使用正确的ImageWriter,您的代码应该按预期工作。

谢谢您详细的解释。但是当我尝试运行这段代码时,它抛出了一个异常,类似于“不支持压缩”...有什么想法吗? - Gnaniyar Zubair
是的。你没有应用 if (param.canWriteCompressed()) 这一行代码。更重要的是,你没有正确安装JAI,所以你仍然没有使用CLibPNGImageWriter。尝试打印writer的类,或使用调试器进行验证。 - Harald K
它运行良好。但是在压缩(32位)输出PNG图像后,图像的大小比源图像增加了。 - Gnaniyar Zubair
它会使大小增加两倍或三倍,而不是减小。 - Gnaniyar Zubair
1
2x-3x的尺寸是什么?另一个PNG文件吗?还是JPEG文件? - Harald K

0
请确认此代码适用于您。
ImageWriteParam writeParam = writer.getDefaultWriteParam();

writeParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);

writeParam.setCompressionType("PackBits");

writeParam.setCompressionQuality(0.5f);

谢谢。


我尝试了这个解决方案,但它显示Java 11的IllegalArgumentException:未知压缩类型! - Ankur Raiyani

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接