8

I've seen several examples of making compressed JPG images from Java BufferedImage objects by writing to file, but is it possible to perform JPG compression without writing to file? Perhaps by writing to a ByteArrayOutputStream like this?

ImageWriter jpgWriter = ImageIO.getImageWritersByFormatName("jpg").next();
ImageWriteParam jpgWriteParam = jpgWriter.getDefaultWriteParam();
jpgWriteParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
jpgWriteParam.setCompressionQuality(0.7f);

ImageOutputStream outputStream = createOutputStream();
jpgWriter.setOutput(outputStream);
IIOImage outputImage = new IIOImage(image, null, null);

// in this example, the JPG is written to file...
// jpgWriter.write(null, outputImage, jpgWriteParam);
// jpgWriter.dispose();

// ...but I want to compress without saving, such as
ByteArrayOutputStream compressed = ???
Community
  • 1
  • 1
JeffThompson
  • 1,538
  • 3
  • 24
  • 47

1 Answers1

17

Just pass your ByteArrayOutputStream to ImageIO.createImageOutputStream(...) like this:

// The important part: Create in-memory stream
ByteArrayOutputStream compressed = new ByteArrayOutputStream();

try (ImageOutputStream outputStream = ImageIO.createImageOutputStream(compressed)) {

    // NOTE: The rest of the code is just a cleaned up version of your code

    // Obtain writer for JPEG format
    ImageWriter jpgWriter = ImageIO.getImageWritersByFormatName("JPEG").next();

    // Configure JPEG compression: 70% quality
    ImageWriteParam jpgWriteParam = jpgWriter.getDefaultWriteParam();
    jpgWriteParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
    jpgWriteParam.setCompressionQuality(0.7f);

    // Set your in-memory stream as the output
    jpgWriter.setOutput(outputStream);

    // Write image as JPEG w/configured settings to the in-memory stream
    // (the IIOImage is just an aggregator object, allowing you to associate
    // thumbnails and metadata to the image, it "does" nothing)
    jpgWriter.write(null, new IIOImage(image, null, null), jpgWriteParam);

    // Dispose the writer to free resources
    jpgWriter.dispose();
}

// Get data for further processing...
byte[] jpegData = compressed.toByteArray();

PS: By default, ImageIO will use disk caching when creating your ImageOutputStream. This may slow down your in-memory stream writing. To disable it, use ImageIO.setCache(false) (disables disk caching globally) or explicitly create an MemoryCacheImageOutputStream (local), like this:

ImageOutputStream outputStream = new MemoryCacheImageOutputStream(compressed);
Harald K
  • 26,314
  • 7
  • 65
  • 111
  • Thanks @haraldK! I'm not totally sure what's happening here. If the JPG data is compressed and written to the `ByteArrayOutputStream` named `compressed`, what is the `IIOImage` section doing? – JeffThompson Jun 09 '16 at 15:32
  • 1
    @JeffThompson I updated the answer for you. The `IIOImage` doesn't "do" anything, it just contains the data you want to write. If you want to learn more, I suggest you read the API documentation or follow a tutorial. :-) – Harald K Jun 09 '16 at 16:05
  • Thanks again! I did look at the docs, but all the different objects for storing and manipulating file streams is really confusing. This makes way more sense! I did run into errors running out of memory without using the `MemoryCacheImageOutputStream`, FYI. – JeffThompson Jun 09 '16 at 16:25
  • How to write a unit test on this method? any suggestion? – Ravi S. Oct 22 '21 at 07:00
  • That is question that probably deserves a longer answer than a comment... Ask it (as an SO question)! Or you can have a look at the tests in [TwelveMonkeys ImageIO](https://github.com/haraldk/TwelveMonkeys) for inspiration, it should contain many tests for code similar to this. – Harald K Oct 22 '21 at 07:07