【问题标题】:Setting jpg compression level with ImageIO in Java在 Java 中使用 ImageIO 设置 jpg 压缩级别
【发布时间】:2013-06-11 02:06:41
【问题描述】:

我正在使用 javax.imageio.ImageIOBufferedImage 保存为 jpeg 文件。 特别是,我创建了以下 Java 函数:

public static void getScreenShot(BufferedImage capture, Path folder, String filename) {
        try {
            ImageIO.write(capture, "jpeg", new File(folder.toString()+"/"+filename+".jpg"));
        } catch (AWTException | IOException ex) {
            Logger.getLogger(ScreenShotMaker.class.getName()).log(Level.SEVERE, null, ex);
        }
}

与任何图像处理软件一样,我希望更改 jpeg 文件的压缩级别。但是,我正在寻找ImageIO 中似乎缺少的这个选项。

我可以设置压缩级别吗?如何设置?

【问题讨论】:

    标签: java jpeg image-compression javax.imageio


    【解决方案1】:

    您必须使用JPEGImageWriteParam,然后使用ImageWriter.write() 保存图像。写之前,通过ImageWriter.setOutput设置输出。

    设置压缩级别如下:

    JPEGImageWriteParam jpegParams = new JPEGImageWriteParam(null);
    jpegParams.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
    jpegParams.setCompressionQuality(1f);
    

    1f 是一个浮点数,代表 100% 质量。如果我没记错的话,默认值是大约 70%

    编辑

    然后,您必须执行以下操作才能获取ImageWriter 的实例。有两种方法,一种是短的,一种是长的(我都保留了,以防万一)。

    捷径(由lapo在一条评论中建议)是:

    final ImageWriter writer = ImageIO.getImageWritersByFormatName("jpg").next();
    // specifies where the jpg image has to be written
    writer.setOutput(new FileImageOutputStream(
      new File(folder.toString() + "/" + filename + ".jpg")));
    
    // writes the file with given compression level 
    // from your JPEGImageWriteParam instance
    writer.write(null, new IIOImage(capture, null, null), jpegParams);
    

    更长的路

    // use IIORegistry to get the available services
    IIORegistry registry = IIORegistry.getDefaultInstance();
    // return an iterator for the available ImageWriterSpi for jpeg images
    Iterator<ImageWriterSpi> services = registry.getServiceProviders(ImageWriterSpi.class,
                                                     new ServiceRegistry.Filter() {   
            @Override
            public boolean filter(Object provider) {
                if (!(provider instanceof ImageWriterSpi)) return false;
    
                ImageWriterSpi writerSPI = (ImageWriterSpi) provider;
                String[] formatNames = writerSPI.getFormatNames();
                for (int i = 0; i < formatNames.length; i++) {
                    if (formatNames[i].equalsIgnoreCase("JPEG")) {
                        return true;
                    }
                }
    
                return false;
            }
        },
       true);
    //...assuming that servies.hasNext() == true, I get the first available service.
    ImageWriterSpi writerSpi = services.next();
    ImageWriter writer = writerSpi.createWriterInstance();
    
    // specifies where the jpg image has to be written
    writer.setOutput(new FileImageOutputStream(
      new File(folder.toString() + "/" + filename + ".jpg")));
    
    // writes the file with given compression level 
    // from your JPEGImageWriteParam instance
    writer.write(null, new IIOImage(capture, null, null), jpegParams);
    

    【讨论】:

    • ImageWriter writer = ImageIO.getImageWritersByFormatName("jpg").next();
    • 不要忘记关闭 FileImageOutputStream 或处置 ImageWriter
    • 有谁知道我在哪里可以参考使用的确切默认值?
    • @brad 使用 jpegParams.getCompressionQuality() 而不设置它。 (你仍然需要先设置jpegParams.setCompressionMode(ImageWriteParam.MODE_EXPLICIT)。)至少对我来说,默认是0.75
    【解决方案2】:

    更简洁的方法是直接从ImageIO获取ImageWriter

    ImageWriter jpgWriter = ImageIO.getImageWritersByFormatName("jpg").next();
    ImageWriteParam jpgWriteParam = jpgWriter.getDefaultWriteParam();
    jpgWriteParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
    jpgWriteParam.setCompressionQuality(0.7f);
    
    ImageOutputStream outputStream = createOutputStream(); // For example implementations see below
    jpgWriter.setOutput(outputStream);
    IIOImage outputImage = new IIOImage(image, null, null);
    jpgWriter.write(null, outputImage, jpgWriteParam);
    jpgWriter.dispose();
    

    需要调用ImageWriteParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT) 才能显式设置压缩级别(质量)。

    ImageWriteParam.setCompressionQuality() 1.0f 是最高质量,最低压缩,而0.0f 是最低质量,最高压缩。

    ImageWriter.setOutput 应该传递一个ImageOutputStream。虽然该方法接受Object,但根据文档,它通常不受支持:

    使用除ImageOutputStream 之外的通用Object 旨在供直接与输出设备或成像协议交互的编写者使用。合法类的集合由作者的服务提供者的getOutputTypes 方法公布;大多数作者将返回一个仅包含ImageOutputStream.class 的单元素数组,以表明他们只接受ImageOutputStream

    大多数情况应该由这两个类来处理:

    • FileImageOutputStream - ImageOutputStream 的实现,将其输出直接写入 FileRandomAccessFile
    • MemoryCacheImageOutputStream - ImageOutputStream 的实现,将其输出写入常规 OutputStream。通常与ByteArrayOutputStream 一起使用(感谢您的提示,@lmiguelmh!)。

    【讨论】:

    • +1,不知道为什么手动过滤更复杂的是1.已发布,2.接受,3.这么长时间没有改进......
    • 不想写磁盘的朋友:ByteArrayOutputStream baos = new ByteArrayOutputStream(); writer.setOutput(new MemoryCacheImageOutputStream(baos)); ... baos.flush(); byte[] returnImage = baos.toByteArray(); baos.close();
    • 令我惊讶的是,我回到了同样的问题,我现在无法编辑我的评论。如果你不按照我之前说的做,直接使用ByteArrayOutputStream 你会得到java.lang.IllegalArgumentException: Illegal output type!
    • @lmiguelmh:自创建后 5 分钟后您将无法编辑您的评论。另外,我将更新我的答案以解决输出流的问题,感谢您提出。
    • 我使用了jpgWriter.setOutput(ImageIO.createImageOutputStream(new File(path))),因为FileImageOutputStream 出了点问题。
    【解决方案3】:

    更通用的方法是(来自Igor's answer):

    static void saveImage(BufferedImage image,File jpegFiletoSave,float quality) throws IOException{
        // save jpeg image with specific quality. "1f" corresponds to 100% , "0.7f" corresponds to 70%
    
            ImageWriter jpgWriter = ImageIO.getImageWritersByFormatName("jpg").next();
            ImageWriteParam jpgWriteParam = jpgWriter.getDefaultWriteParam();
            jpgWriteParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
            jpgWriteParam.setCompressionQuality(quality);
    
            jpgWriter.setOutput(ImageIO.createImageOutputStream(jpegFiletoSave));
            IIOImage outputImage = new IIOImage(image, null, null);
            jpgWriter.write(null, outputImage, jpgWriteParam);
            jpgWriter.dispose();
    
        }
    

    【讨论】:

      【解决方案4】:

      在我的古老图书馆中找到了相同的方法:

      /**
       * Work method.
       * Reads the jpeg image in rendImage, compresses the image, and writes it back out to outfile.
       * JPEGQuality ranges between 0.0F and 1.0F, 0-lowest, 1-highest. ios is closed internally
       *
       * @param rendImage   [@link RenderedImage} instance with an Rendered Image
       * @param ios         {@link ImageOutputStream} instance,
       *                    note that it is disposed in this method
       * @param JPEGQuality float value for the JPEG compression quality (0..1(max))
       * @return {@code true} if image was successfully compressed
       *         else {@code false} on any error, e.g. bad (null) parameters
       */
      public static boolean compressJpegFile( RenderedImage rendImage, ImageOutputStream ios, float JPEGQuality )
      {
          if ( rendImage == null )
              return false;
          if ( ios == null )
              return false;
          if ( ( JPEGQuality <= 0.0F ) || ( JPEGQuality > 1.0F ) )
              return false;
          ImageWriter writer = null;
          try
          {
      
              // Find a jpeg writer
              Iterator iter = ImageIO.getImageWritersByFormatName( "jpg" );
              if ( iter.hasNext() )
                  writer = (ImageWriter) iter.next();
      
              if ( writer == null )
                  throw new IllegalArgumentException( "jpg writer not found by call to ImageIO.getImageWritersByFormatName( \"jpg\" )" );
              writer.setOutput( ios );
      
              // Set the compression quality
              ImageWriteParam iwparam = new MyImageWriteParam();
              iwparam.setCompressionMode( ImageWriteParam.MODE_EXPLICIT );
              iwparam.setCompressionQuality( JPEGQuality );
              //      float res = iwparam.getCompressionQuality();
      
              // Write the image
              writer.write( null, new IIOImage( rendImage, null, null ), iwparam );
      
              return true;
          }
          catch ( Exception e )
          {
              return false;
          }
          finally
          {
              if ( writer != null )
                  writer.dispose();
              // Cleanup
              try
              {
                  ios.flush();
                  ios.close();
              }
              catch ( IOException e )
              {
              }
          }
      }
      

      【讨论】:

        【解决方案5】:

        如果您需要 byte[] 输出,请参阅下面@user_3pij 答案的修改版本:

            private static byte[] compressImageToByteArray(BufferedImage image, float quality)
                throws IOException {
            // save jpeg image with specific quality. "1f" corresponds to 100% , "0.7f" corresponds to
            // 70%
        
            ImageWriter jpgWriter = ImageIO.getImageWritersByFormatName("jpg").next();
            ImageWriteParam jpgWriteParam = jpgWriter.getDefaultWriteParam();
            jpgWriteParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
            jpgWriteParam.setCompressionQuality(quality);
        
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ImageOutputStream ios = ImageIO.createImageOutputStream(bos);
            jpgWriter.setOutput(ios);
            IIOImage outputImage = new IIOImage(image, null, null);
            jpgWriter.write(null, outputImage, jpgWriteParam);
            byte[] result = bos.toByteArray();
            jpgWriter.dispose();
            return result;
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2010-09-21
          • 2012-09-19
          • 1970-01-01
          • 1970-01-01
          • 2023-04-05
          • 1970-01-01
          • 1970-01-01
          • 2017-12-03
          相关资源
          最近更新 更多