Parisana Ng
Parisana Ng

Reputation: 517

Compressing/Reducing image file in java

One of the image file used

    String dirName=System.getProperty("user.home") + "/deleteme";
    ByteArrayOutputStream byteArrayOutputStream=new ByteArrayOutputStream(1000);
    BufferedImage bufferedImage=ImageIO.read(new File(dirName,"a.jpg"));
    ImageIO.write(bufferedImage, "jpg", byteArrayOutputStream);
    byteArrayOutputStream.flush();

    byteArrayOutputStream.close();

    byte[] bytearray = byteArrayOutputStream.toByteArray();

    BufferedImage imag=ImageIO.read(new ByteArrayInputStream(bytearray));
    ImageIO.write(imag, "jpg", new File(dirName,"snap.jpg"));

This piece of code reads the image file, converts it into byteArray and then stores it back. Now for most of the image files I tested, the size reduces drastically to just about 15% of the original size.

  1. Can someone explain why doing this reduces the image file size.

  2. Is there a drop in quality in this process.

[I have compared the images in http://driiqm.mpi-inf.mpg.de and it shows me that the new image has the same quality.]

Upvotes: 1

Views: 1380

Answers (3)

Piro
Piro

Reputation: 1435

TLDR: Your result does not have same quality as original. JPEG always looses some quality but your result looses a lot of it. Exif metadata have been lost but it is marginal difference.

I tested your example image with your writing method and then tested writing it with top quality (For example code that @MauricePerry wrote). Then I compared them with http://driiqm.mpi-inf.mpg.de and with http://exif.regex.info/exif.cgi

So original file:

With your method I got:

  • size of 35kB
  • no embedded color profile
  • Sampling YCbCr4:2:0 (2 2)
  • a lot of differences between original and result:

comparison with first result

With top quality I got:

  • size of 272kB
  • no embedded color profile
  • Sampling YCbCr4:2:0 (2 2)
  • minumum of differences between original and result (but still there are some):

comparison with second result

I am not sure how did you assume images have same quality, because webpage you posted shows lot of differences. Even with top quality it shows differences and that makes sense because JPEG is lossy format. BufferedImage represents image data as bitmap and some information is lost when converting to JPEG. That is why we have lossless formats like PNG.

I also did what you said - used result image as source image and then input and output are same. I would compare it to reaching local minimum. JPEG -> bitmap conversion produces bitmap where bitmap -> JPEG produces same JPEG data because you are using same function (same quality, same sampling). But if bitmap -> JPEG conversion used different function (for example using top quality this time) there would be more loss of quality.

Upvotes: 1

matt
matt

Reputation: 12346

The quality is being changed to the default value. You write with this default value twice.

ImageIO.write(bufferedImage, "jpg", byteArrayOutputStream);

Now the image has been turned to bytes, and compressed using a lossy compression.

ImageIO.write(imag, "jpg", new File(dirName,"snap.jpg"));

That also uses the default compression.

try (ByteArrayOutputStream out = new ByteArrayOutputStream(1000)) {
        float quality = 1f;
        ImageWriter writer = ImageIO.getImageWritersBySuffix("jpg").next();

        ImageWriteParam param = writer.getDefaultWriteParam();
        param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
        param.setCompressionQuality(1f);
        IIOImage image = new IIOImage(img, new ArrayList<>(), null);
        writer.setOutput(ImageIO.createImageOutputStream(out));
        writer.write(null, image, param);
        byte[] bytearray = out.toByteArray();
        System.out.println(bytearray.length);
        Files.write(Paths.get("snap2.jpg"), bytearray, StandardOpenOption.WRITE);

    }

In this example the byte array will contain the least lossy compression, which is close to the input size. To write it out, don't use ImageIO to read/write it again. Instead, I just wrote the bytes directly to the file.

Upvotes: 1

Maurice Perry
Maurice Perry

Reputation: 9658

You would do something like this:

try (OutputStream out = new FileOutputStream(new File(dirName,"snap.jpg"))) {
    JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(out);
    JPEGEncodeParam eparm = encoder.getDefaultJPEGEncodeParam(img);
    eparm.setQuality(quality, true);
    encoder.setJPEGEncodeParam(eparm);
    encoder.encode(img);
}

where quality is a float between 0. and 1.

Upvotes: 1

Related Questions