Compression of PNG using JAI (Java Advanced Imaging)

Gnaniyar Zubair picture Gnaniyar Zubair · Jul 2, 2014 · Viewed 9k times · Source

I am using javax.imageio API and JAI for compressing different types of images. It works fine for JPEG using JPEGImageWriter and GIF using GIFImageWriter. But it is not supporting for PNG compression using PNGImageWriter which throws an exception like compression type is not set or "No valid compression", etc. So I used this below ImageWriter for PNG. It works but image quality is too bad.

Can anyone suggest how to use PNGImageWriter for PNG compression and which JAR contains it?

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

Answer

Harald K picture Harald K · Jul 4, 2014

It seems the default com.sun.imageio.plugins.png.PNGImageWriter that is bundled with the JRE does not support setting compression. This is kind of surprising, as the format obviously supports compression. However, the PNGImageWriter always writes compressed.

You can see from the source code that it uses:

Deflater def = new Deflater(Deflater.BEST_COMPRESSION);

Which will give you good, but slow compression. It might be good enough for you, but for some cases it might be better to use faster compression and larger files.

To fix your code, so that it would work with any format name, change the lines:

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

to:

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

It will still write compressed PNGs.

If you need more control over the PNG compression, like setting compression or filter used, you need to find an ImageWriter that supports it. As you mention JAI, I think the CLibPNGImageWriter that is part of jai-imageio.jar or jai-imageio-tools.jar supports setting compression. You just need to look through the ImageWriters iterator, to see if you have it installed:

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

With the correct ImageWriter, your code should work as expected.