15 February 2011

How to change JPEG compression in Java

Unless you use specialized libraries like JMagick, JAI, or access libjpeg through JNI, doing something simple like changing the JPEG compression in Java turned out to be excruciatingly tedious (or just hard to figure out the first time at least).  Here's how to do it:

(I'll explain how to do the following in Clojure as well.)

And the upshot is that this way, it seems you get to preserve the metadata from the original JPEG file — at least it did for me!

You will need these imports:

import java.io.File;
import javax.imageio.*;
import javax.imageio.stream.*;

First, we're going to need the default JPEG ImageReader and ImageWriter:

ImageReader imgRdr = ImageIO.getImageReadersByFormatName("jpg").next();
ImageWriter imgWrtr = ImageIO.getImageWritersByFormatName("jpg").next();

Next we'll need an ImageInputStream, wrapping around the JPEG File you want to convert the compression level of, and an ImageOutputStream, wrapping around the File location you want to save the re-compressed JPEG to:

ImageInputStream imgInStrm = ImageIO.createImageInputStream(inputJpegFile);
ImageOutputStream imgOutStrm = ImageIO.createImageInputStream(outputFile);

I assume you know how to create a File object to supply the parameters inputJpegFile and outputFile.

Now we mate the streams to the reader/writer:


imgRdr.setInput(imgInStrm);
imgWrtr.setOutput(imgOutStrm);

We're going to read in the JPEG file now and store it into the IIOImage container — this will contain the metadata of the original JPEG:

IIOImage iioImg = IIOImage(imgRdr.read(0), null, imgRdr.getImageMetadata(0));

Now here's the part where we select the compression quality.  The quality number must be between 0 and 1, where 1 means least-compression/highest-quality and 0 means max-compression/lowest-quality (here I use 0.8):

ImageWriteParam jpgWrtPrm = imgWrtr.getDefaultWriteParam();
jpgWrtPrm.setCompressionMode(JPEGImageWriteParam/MODE_EXPLICIT);
jpgWrtPrm.setCompressionQuality(0.8);

Finally we can write the image out!

imgWrtr.write(null, iioImg, jpgWrtPrm);

Before we go, we have to clean up all the resources used:

imgInStrm.close();
imgOutStrm.close();
imgWrtr.dispose();
imgRdr.dispose();

And that's it!  So here's the recap:

import java.io.File;
import javax.imageio.*;
import javax.imageio.stream.*;

File inputJpegFile = ... // your input JPEG file to re-compress at a different quality
File outputFile = ... // where you want the new JPEG file to be written to

// set up reader/writer to wrap around files
ImageReader imgRdr = ImageIO.getImageReadersByFormatName("jpg").next();
ImageWriter imgWrtr = ImageIO.getImageWritersByFormatName("jpg").next();
ImageInputStream imgInStrm = ImageIO.createImageInputStream(inputJpegFile);
ImageOutputStream imgOutStrm = ImageIO.createImageInputStream(outputFile);
imgRdr.setInput(imgInStrm);
imgWrtr.setOutput(imgOutStrm);

// read in JPEG into IIOImage container 
IIOImage iioImg = IIOImage(imgRdr.read(0), null, imgRdr.getImageMetadata(0)); 

// set compression level 
ImageWriteParam jpgWrtPrm = imgWrtr.getDefaultWriteParam();
jpgWrtPrm.setCompressionMode(JPEGImageWriteParam/MODE_EXPLICIT);
jpgWrtPrm.setCompressionQuality(0.8);

// write out JPEG
imgWrtr.write(null, iioImg, jpgWrtPrm);

// clean up 
imgInStrm.close();
imgOutStrm.close();

imgWrtr.dispose();
imgRdr.dispose();

I should note that the above is untested Java code — I wrote the equivalent of the above in Clojure instead.

Also, I spent so much time looking around the internet for clues as to how to do this, I figure I should dump the links I found useful here too, for my own sake:
Well, hope that helps!

No comments: