简体   繁体   English

用Java压缩/缩小图像文件

[英]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. 这段代码读取图像文件,将其转换为byteArray,然后将其存储回去。 Now for most of the image files I tested, the size reduces drastically to just about 15% of the original size. 现在,对于我测试过的大多数图像文件,其大小都急剧减小到仅原始大小的15%。

  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.] [我在http://driiqm.mpi-inf.mpg.de中比较了这些图像,它显示出新图像具有相同的质量。]

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. 质量是介于0和1之间的浮点数。

TLDR: Your result does not have same quality as original. TLDR:您的结果与原始质量不同。 JPEG always looses some quality but your result looses a lot of it. JPEG总是会降低一些质量,但是您的结果会降低很多质量。 Exif metadata have been lost but it is marginal difference. Exif元数据已丢失,但这只是微不足道的差异。

I tested your example image with your writing method and then tested writing it with top quality (For example code that @MauricePerry wrote). 我用编写方法测试了示例图像,然后测试了高质量的图像(例如@MauricePerry编写的代码)。 Then I compared them with http://driiqm.mpi-inf.mpg.de and with http://exif.regex.info/exif.cgi 然后,我将它们与http://driiqm.mpi-inf.mpg.dehttp://exif.regex.info/exif.cgi进行了比较

So original file: 因此原始文件:

With your method I got: 用你的方法我得到:

  • size of 35kB 大小35kB
  • no embedded color profile 没有嵌入的颜色配置文件
  • Sampling YCbCr4:2:0 (2 2) 采样 YCbCr4:2:0(2 2)
  • a lot of differences between original and result: 原始和结果之间有很多差异:

与第一结果比较

With top quality I got: 以最高的质量我得到:

  • size of 272kB 大小272kB
  • no embedded color profile 没有嵌入的颜色配置文件
  • Sampling YCbCr4:2:0 (2 2) 采样 YCbCr4:2:0(2 2)
  • minumum of differences between original and result (but still there are some): 原始和结果之间的差异最小(但仍然有一些差异):

与第二结果比较

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. 即使具有最高质量,它也会显示出差异,这是有道理的,因为JPEG是有损格式。 BufferedImage represents image data as bitmap and some information is lost when converting to JPEG. BufferedImage将图像数据表示为位图,并且在转换为JPEG时会丢失一些信息。 That is why we have lossless formats like PNG. 这就是为什么我们拥有无损格式(如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). JPEG->位图转换会生成位图,其中位图-> JPEG会生成相同的JPEG数据,因为您使用的是相同的功能(相同的质量,相同的采样)。 But if bitmap -> JPEG conversion used different function (for example using top quality this time) there would be more loss of quality. 但是,如果位图-> JPEG转换使用其他功能(例如,这次使用最高质量),则会导致质量损失更大。

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. 要写出来,不要使用ImageIO再次读/写它。 Instead, I just wrote the bytes directly to the file. 相反,我只是将字节直接写到文件中。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM