3

I am converting an Image into byte[] using following code.

public static byte[] extractBytes (String ImageName) throws IOException {

   ByteArrayOutputStream baos=new ByteArrayOutputStream();
        BufferedImage img=ImageIO.read(new File(ImageName));
        ImageIO.write(img, "jpg", baos);
        return baos.toByteArray();
    }

Now when I am testing my code:

public static void main(String[] args) throws IOException {
    String filepath = "image_old.jpg";
    File outp=new File(filepath);
    System.out.println("Size of original image="+outp.length());
    byte[] data = extractBytes(filepath);
    System.out.println("size of byte[] data="+data.length);
    BufferedImage img = ImageIO.read(new ByteArrayInputStream(data));
    //converting the byte[] array into image again
    File outputfile = new File("image_new.jpg");
    ImageIO.write(img, "jpeg", outputfile);
    System.out.println("size of converted image="+outputfile.length());
}

I am getting very strange results:

Size of original image=78620 
size of byte[] data=20280
size of converted image=20244

After converting image into byte[], its size getting decreased by around 1/4th and also when I am converting byte[] back to image its size alters.But output image is successfully getting created in the desired location. I can see the slight difference in quality of the original image and new image after doing 500-600 % zoom in. New image is little blurred after zoom in.

Here is the image on which I am doing the testing http://pbrd.co/1BrOVbf

Please explain the reason of this change in size and also I want to know any method to get the same size after this.

Harsh Sharma
  • 10,942
  • 2
  • 18
  • 29
  • That's just how compression works. If you compress the same thing twice, you might not get the exact same compressed file twice. – user253751 Mar 26 '15 at 08:13
  • Also, when you compress an image using JPEG, the JPEG file has less quality than the original image. – user253751 Mar 26 '15 at 08:14
  • @immibis, but why it is getting compressed even I have not added any compression code. Can you explain in detail what is happening here. I am confused. – Harsh Sharma Mar 26 '15 at 08:17
  • @immibis, yeah the quality is also getting decreased. I have realized after doing 600% zoom on both images. :P – Harsh Sharma Mar 26 '15 at 08:20
  • ImageIO with read and write provides a plug-in mechanism (for several file types). One can get the ImageWriter for JPEG and write with options set, like high quality. – Joop Eggen Mar 26 '15 at 08:31
  • @HarshSharma The JPEG format is a compressed format, and a lossy format (which simply means that it loses quality). So you are (well, the ImageIO system is) compressing the image every time you save it as JPEG. – user253751 Mar 26 '15 at 08:40
  • @immibis But the original image was already a jpg only. so it should already be in compressed form, right ? I am reading the jpeg in BufferedImage and reconverting the image into jpeg. jpeg to jpeg, still change in size ? – Harsh Sharma Mar 26 '15 at 08:45
  • @HarshSharma See my answer for the most likely reason this happens. Also, please link your original file, if possible. What you see is completely normal, don't worry. ;-) PS: You should also make it clear in your "question" (there isn't one at the moment) whether you want to know *why* this happens (which I tried to answer) or *how to fix* (describe your goal, why it does not work, and the expected behaviour). – Harald K Mar 26 '15 at 09:41
  • @HarshSharma *Reading* a JPEG decompresses it. BufferedImage does not store JPEG-compressed images. – user253751 Mar 26 '15 at 18:17

2 Answers2

5

The image you have is compressed with maximum quality setting ("100%" or 1.0 in ImageIO terms). JPEG compression isn't very effective at such high settings, and is thus quite a bit larger than usual. When using ImageIO.write(..., "JPEG", ...) the default quality setting will be used. This default is 0.75 (the exact meaning of such a value is encoder dependent though, and isn't exact science), and thus lower quality, resulting in a smaller file size.

(Another likely cause for such a significant decrease in file size between the original and the re-compressed image, is the removal of meta data. When reading using ImageIO.read(file) you are effectively stripping away any meta data in the JPEG file, like XMP, Exif or ICC profiles. In extreme cases (yes, I'm talking mainly about Photoshop here ;-)) this meta data can take up more space than the image data itself (ie. megabytes of meta data is possible). This is however, not the case for your file.)

As you can see from the second re-compression (from byte[] to final output file), the output is just slightly smaller than the input. This is because the quality setting (unspecified, so still using default) will be the same in both cases (also, any metadata would also be lost in this step, so not adding to the file size). The minor difference is likely due to some small losses (rounding errors etc) in the JPEG decompression/re-compression.

While slightly counter-intuitive, the least data-loss (in terms of change from the original image, not in file size) when re-compression a JPEG, is always achieved by re-compression with the same quality setting (using the exact same tables should be virtually lossless, but small rounding errors might still occur) as the original. Increasing the quality setting will make the file output larger, but the quality will actually degrade.

The only way to be 100% sure to not lose any data or image quality, is by not decoding/encoding the image in the first place, but rather just copy the file byte by byte, for instance like this:

File in = ...;
File out = ...;

InputStream input = new FileInputStream(in);
try {
    OutputStream output = new FileOutputStream(out);
    try {
        copy(input, output);
    }
    finally {
        output.close();
    }
}
finally {
    input.close();
}

And the copy method:

public void copy(final InputStream in, final OutputStream out) {
    byte[] buffer = new byte[1024]; 
    int count;

    while ((count = in.read(buffer)) != -1) {
        out.write(buffer, 0, count);
    }

    // Flush out stream, to write any remaining buffered data
    out.flush();
}
Harald K
  • 26,314
  • 7
  • 65
  • 111
  • hey, I have edited my answer and added the image on which I am doing the test. – Harsh Sharma Mar 26 '15 at 11:31
  • Yeah thats method I am using now. Thanks (Y) – Harsh Sharma Mar 27 '15 at 12:06
  • i am late here.. but for another info.. when using `ImageIO.write`, i got a the image file rotated upside down and a large size image (2.3 Mb --> 23 Mb).. i know im doing some thing wrong.. but can't find... https://stackoverflow.com/questions/62473381/java-byte-array-to-image-file-creation-results-a-rotated-upside-down-image-file – Bhanuchander Udhayakumar Jun 20 '20 at 08:08
  • @BhanuchanderUdhayakumar Most likely your original file has Exif information, specifying image orientation. Most software will rotate the image for you according to this. ImageIO doesn’t. So when you write the image again without Exif, the orientation will no longer be applied and the image will appear upside down or just 90deg rotated. – Harald K Jun 20 '20 at 09:21
  • @haraldK Thanks for your response. also i got the image file with size increased. (approx around 10x times). How this happen in byte array.. – Bhanuchander Udhayakumar Jun 20 '20 at 09:43
1

When you call ImageIO.write(img, "jpeg", outputfile); the ImageIO library writes a jpeg image, using its own compression parameters. The output image appears to be more compressed than the input image. You can adjust the level of compression by changing the parameter in the call to jpegParams.setCompressionQuality below. The resulting file may be bigger or smaller than the original depending on the relative compression levels in each.

public static ImageWriter getImageWriter() throws IOException {
    IIORegistry registry = IIORegistry.getDefaultInstance();
    Iterator<ImageWriterSpi> services = registry.getServiceProviders(ImageWriterSpi.class, (provider) -> {
        if (provider instanceof ImageWriterSpi) {
            return Arrays.stream(((ImageWriterSpi) provider).getFormatNames()).anyMatch(formatName -> formatName.equalsIgnoreCase("JPEG"));
        }
        return false;
    }, true);
    ImageWriterSpi writerSpi = services.next();
    ImageWriter writer = writerSpi.createWriterInstance();
    return writer;
}

public static void main(String[] args) throws IOException {
    String filepath = "old.jpg";
    File outp = new File(filepath);
    System.out.println("Size of original image=" + outp.length());
    byte[] data = extractBytes(filepath);
    System.out.println("size of byte[] data=" + data.length);
    BufferedImage img = ImageIO.read(new ByteArrayInputStream(data));
    File outputfile = new File("new.jpg");
    JPEGImageWriteParam jpegParams = new JPEGImageWriteParam(null);
    jpegParams.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
    jpegParams.setCompressionQuality(1f);
    ImageWriter writer = getImageWriter();
    outputfile.delete();
    try (final ImageOutputStream stream = createImageOutputStream(outputfile)) {
        writer.setOutput(stream);
        try {
            writer.write(null, new IIOImage(img, null, null), jpegParams);
        } finally {
            writer.dispose();
            stream.flush();
        }
    }
    System.out.println("size of converted image=" + outputfile.length());
}

This solution is adapted from the answer by JeanValjean given here Setting jpg compression level with ImageIO in Java

Community
  • 1
  • 1
mikeyreilly
  • 6,523
  • 1
  • 26
  • 21
  • Hey thanks for the wonderful reply, but I am still confused if I am using jpegParams.setCompressionQuality(1f), still my image size is getting changed(getting increased in this case). Why my new image size is different from original image size ? – Harsh Sharma Mar 26 '15 at 12:19
  • @HarshSharma Because you are still decoding/re-encoding the JPEG. Instead, if you want an exact copy, do as I suggest in my answer. Just copy the data, as any other binary. – Harald K Mar 26 '15 at 12:27