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

后端 未结 2 1313
日久生厌
日久生厌 2021-01-24 00:23

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

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

   ByteArrayOutputStream b         


        
相关标签:
2条回答
  • 2021-01-24 00:34

    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();
    }
    
    0 讨论(0)
  • 2021-01-24 00:36

    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

    0 讨论(0)
提交回复
热议问题