用 javax.imageio 实现图片压缩原理是啥?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了用 javax.imageio 实现图片压缩原理是啥?相关的知识,希望对你有一定的参考价值。

参考技术A 下面是传进byte[],传出byte[]数组的,稍微修改即可即可,(data为传过来的byte[]数组)
Java代码
ByteArrayInputStream is = new ByteArrayInputStream(data);

BufferedImage src = null;
ByteArrayOutputStream out = null;
ImageWriter imgWrier;
ImageWriteParam imgWriteParams;

// 指定写图片的方式为 jpg
imgWrier = ImageIO.getImageWritersByFormatName("jpg").next();
imgWriteParams = new javax.imageio.plugins.jpeg.JPEGImageWriteParam(null);

// 要使用压缩,必须指定压缩方式为MODE_EXPLICIT
imgWriteParams.setCompressionMode(imgWriteParams.MODE_EXPLICIT);
// 这里指定压缩的程度,参数qality是取值0~1范围内,
imgWriteParams.setCompressionQuality((float)0.1/data.length);

imgWriteParams.setProgressiveMode(imgWriteParams.MODE_DISABLED);
ColorModel colorModel = ColorModel.getRGBdefault();
// 指定压缩时使用的色彩模式
imgWriteParams.setDestinationType(new javax.imageio.ImageTypeSpecifier(colorModel, colorModel
.createCompatibleSampleModel(16, 16)));

try
src = ImageIO.read(is);
out = new ByteArrayOutputStream(data.length);

imgWrier.reset();
// 必须先指定 out值,才能调用write方法, ImageOutputStream可以通过任何 OutputStream构造
imgWrier.setOutput(ImageIO.createImageOutputStream(out));
// 调用write方法,就可以向输入流写图片
imgWrier.write(null, new IIOImage(src, null, null), imgWriteParams);

out.flush();
out.close();
is.close();
data = out.toByteArray();
catch(Exception e)
e.printStackTrace();

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

【中文标题】在 Java 中使用 ImageIO 设置 jpg 压缩级别【英文标题】:Setting jpg compression level with ImageIO in Java 【发布时间】: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 中似乎缺少的这个选项。

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

【问题讨论】:

【参考方案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 Here is the documentation stating that the default value is 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 的实现,将其输出直接写入 FileRandomAccessFileMemoryCacheImageOutputStream - 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;

【讨论】:

以上是关于用 javax.imageio 实现图片压缩原理是啥?的主要内容,如果未能解决你的问题,请参考以下文章

jdk内置类javax.imageio.ImageIO支持的图片处理格式

javax.imageio.IIOException: Can't read input file!完美解决

struts2产生验证码时, 绘图BufferedImage对象 已经弄好了,发送的用啥输出流呢?

javax.imageio.IIOException:无法在 Tomcat 9、OpenJDK 11 和 Geoserver 中创建 ImageInputStream

Java如何实现证件照换底色| 背景换色

Java如何实现证件照换底色| 背景换色