在Java中使用ImageIO设置jpg压缩级别

60

我正在使用javax.imageio.ImageIO将一个BufferedImage保存为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中似乎找不到这个选项。那么,我该如何设置压缩级别呢?
5个回答

69
更简洁的方法是直接从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

这两个类可以处理大多数情况:


4
+1,我想知道为什么更复杂需要手动筛选的内容会被发布、接受并且长时间内没有得到改进。 - Marco13
11
对于那些不想写入磁盘的人:ByteArrayOutputStream baos = new ByteArrayOutputStream(); writer.setOutput(new MemoryCacheImageOutputStream(baos)); ... baos.flush(); byte[] returnImage = baos.toByteArray(); baos.close();解释:这是一段Java代码,用于将图像数据保存在内存中而不是写入磁盘。首先创建一个字节数组输出流 ByteArrayOutputStream,然后将其设置为图像的输出流 writer.setOutput(new MemoryCacheImageOutputStream(baos))。接着将图像数据写入输出流,并使用 baos.flush() 刷新缓冲区。最后,使用 baos.toByteArray() 将数据转换为字节数组并返回,关闭输出流 baos.close() - lmiguelmh
4
让我惊讶的是我回到了同样的问题,但现在我无法编辑我的评论。如果你不按照我之前说的那样直接使用ByteArrayOutputStream,你将会得到一个java.lang.IllegalArgumentException: Illegal output type!的错误提示。 - lmiguelmh
1
@lmiguelmh:自创建后5分钟内您无法编辑评论。另外,我会更新我的答案来解决输出流的问题,感谢提出建议。 - Igor Klimer
1
我使用了 jpgWriter.setOutput(ImageIO.createImageOutputStream(new File(path))),因为 FileImageOutputStream 出现了问题。 - Evgeni Sergeev

59
你需要使用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);

4
ImageWriter writer = ImageIO.getImageWritersByFormatName("jpg").next(); - lapo
5
别忘了关闭FileImageOutputStream或释放ImageWriter。 - Patrick
有人知道我可以在哪里查看默认值的确切引用吗? - brad
4
请使用 jpegParams.getCompressionQuality() 而不要进行设置。(你仍然需要先设置jpegParams.setCompressionMode(ImageWriteParam.MODE_EXPLICIT)。)对我来说,默认值是0.75 - Nnnes
1
这里是文档,说明默认值为0.75。https://docs.oracle.com/javase/8/docs/api/javax/imageio/plugins/jpeg/JPEGImageWriteParam.html#JPEGImageWriteParam-java.util.Locale- - Nnnes
你需要关闭 FileImageOutputStream - mwieczorek

1
更通用的方法是(来自Igor的答案):
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();

    }

0

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

/**
 * 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 )
        {
        }
    }
}

0
如果您需要一个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;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接