Search code examples
javaimagebufferedimagejavax.imageio

Image size getting decreased after converting it into byte[] using BufferedImage and ImageIO


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.


Solution

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