java图片压缩

Posted Master_Shifu_

tags:

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

1背景

查看各种文章,发现thumbnailator的压缩率和压缩效果都不错,thumbnailator又是使用java实现的,所以直接扒源码,找到了里面实现压缩的关键代码,封装成一个压缩工具类,有需要的同学可以参考。thumbnailator里面有用到很多不错的设计模式,后期也会开相应的模块来介绍。

2 使用方法

2.1 如需要解析jpeg图片颜色模式为CMYK的图片,需要导入依赖

<!--imageio-jpeg CMYK模式读取支持-->
<dependency>
	<groupId>com.twelvemonkeys.imageio</groupId>
	<artifactId>imageio-jpeg</artifactId>
	<version>3.6</version>
</dependency>

2.1 工具类使用

1.将目录图片下的所有能压缩的图片使用中等质量压缩到目录图片3,文件名保持不变,输出格式为jpg

String source = "E:\\\\图片压缩\\\\图片";
String traget = "E:\\\\图片压缩\\\\图片3";
// 压缩图片目录下的所有
ThumbnailUtil.of(new File(source).listFiles(ThumbnailUtil.readFilter())).identifyCompress(ThumbnailUtil.ratios[1])
        .outputFormat("jpg").toFiles(new File(traget), null);

2.将目录图片下的所有能压缩的图片使用尺寸不变,质量压缩50%压缩到目录图片3,文件名和格式使用原有输入的文件名和格式输出

String source = "E:\\\\图片压缩\\\\图片";
String traget = "E:\\\\\\图片压缩\\\\图片3";		
ThumbnailUtil.of(new File(source).listFiles(ThumbnailUtil.readFilter())).scale(1D).outputQuality(0.5D)
        .outputFormat(ThumbnailUtil.orgForm).toFiles(new File(traget), "");

3.将图片目录下的原图.jpg使用中等质量压缩到目录图片3,文件名和格式使用原有输入的文件名和格式输出

String source = "E:\\\\图片压缩\\\\图片\\\\原图.jpg";
String traget = "E:\\\\图片压缩\\\\图片3\\\\原图.jpg";
// 压缩图片目录下的所有
ThumbnailUtil.of(new File(source)).identifyCompress(ThumbnailUtil.ratios[1])
        .toFile(new File(traget));	

4.将图片目录下的原图.jpg使用尺寸不压缩,质量压缩到到目标图片40%质量,文件名和格式使用输入的文件名和格式输出

String source = "E:\\\\图片压缩\\\\图片\\\\原图.jpg";
String traget = "E:\\\\图片压缩\\\\图片3\\\\原图.jpg";				
ThumbnailUtil.of(new File(source)).scale(1D).outputQuality(0.4D)

5.将MultipartFile格式的多张图片进行压缩,使用最高质量压缩(尺寸不变,图片质量为原来的0.8)到目录图片3,文件名和格式使用原有输入的文件名和格式输出MultipartFile格式文件(使用该方法最好使用MimetypesFileTypeMap识别一下多个图片格式是否全部为图片,只压缩图片)

MultipartFile[] myFiles = ThumbnailUtil.of(myFiles)
                .identifyCompress(ThumbnailUtil.ratios[0])
                .outputFormat(ThumbnailUtil.orgForm).asMultipartFiles();

2.2 压缩效果

原图尺寸3840*2400,图片大小1311KB:

压缩比率identifyCompress选low低压缩率,尺寸3840*2400,图片大小1025KB

压缩比率identifyCompress选medium中等压缩率,尺寸3072*1920,图片大小491KB

压缩比率identifyCompress选high高压缩率,尺寸2688*1680,图片大小317KB

2.2 压缩工具类

import org.apache.commons.lang3.StringUtils;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.web.multipart.MultipartFile;

import javax.activation.MimetypesFileTypeMap;
import javax.imageio.*;
import javax.imageio.stream.ImageOutputStream;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.List;
import java.util.*;
import java.util.stream.StreamSupport;


public class ThumbnailUtil 

    // 压缩比率, 低(原质量*0.85),中(原质量*0.7),高(原质量*0.6)
    public static String[] ratios = new String[]"low", "medium", "high";
    // 原始格式
    public static String orgForm = "orgForm";

    public static Builder<File> of(File... files) 
        Iterable<File> iter = Arrays.asList(files);
        return new Builder<>(iter);
    

    public static Builder<BufferedImage> of(BufferedImage... images) 
        return new Builder<>(Arrays.asList(images));
    

    public static Builder<InputStream> of(InputStream... inputStreams) 
        return new Builder<>(Arrays.asList(inputStreams));
    

    public static Builder<MultipartFile> of(MultipartFile... multipartFiles) 
        return new Builder<>(Arrays.asList(multipartFiles));
    

    public static FilenameFilter readFilter() 
        String readFormats[] = ImageIO.getReaderFormatNames();
        Set<String> readFormatSet = new HashSet<>(Arrays.asList(readFormats));
        String writeFormats[] = ImageIO.getWriterFormatNames();
        return new FilenameFilter() 
            @Override
            public boolean accept(File dir, String name) 
                String seprator = ".";
                if (name == null || !name.contains(seprator)) 
                    return false;
                
                String format = name.substring(name.lastIndexOf(seprator) + 1);
                return readFormatSet.contains(format);
            
        ;
    

    public static class Builder<T> 
        // 待转换源数据
        private final Iterable<T> sources;
        // 输出格式
        private String outputFormat = null;
        //        // 原图宽
//        private int width = -1;
//        // 原图高
//        private int height = -1;
        // 压缩比率
        private String compressionRatio = null;

        // 缩放后宽
        private double scaleWidth = Double.NaN;
        // 缩放后高
        private double scaleHeight = Double.NaN;
        // 压缩质量系数 0-1之间
        private double outputQuality = Double.NaN;

        private Builder() 
            sources = null;
        

        private Builder(Iterable<T> sources) 
            this.sources = sources;
        

        public Builder<T> identifyCompress(String compressionRatio) 
            if (!Objects.equals(Double.NaN, scaleWidth)
                    || !Objects.equals(Double.NaN, scaleHeight)
                    || !Objects.equals(Double.NaN, outputQuality)
            ) 
                // 有设置scale和outputQuality则不使用自动压缩选项
                return this;
             else if (null == compressionRatio) 
                this.compressionRatio = ratios[1];
                return this;
            
            if (!Arrays.toString(ratios).contains(compressionRatio)) 
                throw new IllegalArgumentException("Unsupported compressionRatio Type.");
            
            this.compressionRatio = compressionRatio;
            return this;
        

        private Builder<T> identifyCompress(String compressionRatio, int width, int height) 
            if (width <= 0 || height <= 0) 
                throw new IllegalArgumentException("Width (" + width + ") and height (" + height + ") cannot be <= 0");
            
            // 为了支持多线程压缩, 需要将可变变量直接传入方法中,不能使用共享变量返回scaleWidth和outputQuality
            if (!Objects.equals(Double.NaN, scaleWidth)
                    || !Objects.equals(Double.NaN, scaleHeight)
                    || !Objects.equals(Double.NaN, outputQuality)
            ) 
                // 有设置scale和outputQuality则不使用自动压缩选项
                return this;
             else if (null == compressionRatio) 
                compressionRatio = ratios[1];
            
            if (!Arrays.toString(ratios).contains(compressionRatio)) 
                throw new IllegalArgumentException("Unsupported compressionRatio Type.");
            
            int min = width < height ? width : height;
            double offset;
            Builder builder = new Builder();
            if (Objects.equals(ratios[0], compressionRatio)) 
                // 最低压缩,图片保持原来尺寸,质量为原来的0.8
                builder.scaleWidth = builder.scaleHeight = 1.0D;
                builder.outputQuality = 0.8D;
                return builder;
             else if (Objects.equals(ratios[1], compressionRatio)) 
                offset = 0.4D;
             else 
                offset = 0.3D;
            
            if (min <= 1024) 
                // 最小像素小于1024,长和宽不压缩
                builder.scaleWidth = builder.scaleHeight = 1.0D;
                builder.outputQuality = (builder.outputQuality = 0.3D + offset) <= 1 ? builder.outputQuality : 1;
             else if (min > 1024 && min <= 3 * 1024) 
                builder.scaleHeight = (builder.scaleHeight = 0.4D + offset) <= 1 ? builder.scaleHeight : 1;
                builder.scaleWidth = builder.scaleHeight;
                builder.outputQuality = (builder.outputQuality = 0.3D + offset) <= 1 ? builder.outputQuality : 1;
             else 
                builder.scaleHeight = (builder.scaleHeight = 2048D / min + offset) <= 1 ? builder.scaleHeight : 1;
                builder.scaleWidth = builder.scaleHeight;
                builder.outputQuality = builder.scaleHeight;
            
            return builder;
        

        public Builder<T> scale(double scaleWidth, double scaleHeight) 
            if (scaleWidth <= 0.0 || scaleHeight <= 0.0) 
                throw new IllegalArgumentException(
                        "The scaling factor is equal to or less than 0."
                );
            
            if (Double.isNaN(scaleWidth) || Double.isNaN(scaleHeight)) 
                throw new IllegalArgumentException(
                        "The scaling factor is not a number."
                );
            
            if (Double.isInfinite(scaleWidth) || Double.isInfinite(scaleHeight)) 
                throw new IllegalArgumentException(
                        "The scaling factor cannot be infinity."
                );
            
            this.scaleWidth = scaleWidth;
            this.scaleHeight = scaleHeight;
            return this;
        

        public Builder<T> scale(double scale) 
            return scale(scale, scale);
        

        public Builder<T> outputQuality(double quality) 
            if (quality < 0.0f || quality > 1.0f) 
                throw new IllegalArgumentException(
                        "The quality setting must be in the range 0.0f and " +
                                "1.0f, inclusive."
                );
            
            outputQuality = quality;
            return this;
        

        public Builder<T> outputFormat(String formatName) 
            if (StringUtils.isEmpty(formatName)) 
                this.outputFormat = orgForm;
                return this;
             else if (Objects.equals(orgForm, formatName)) 
                this.outputFormat = formatName;
                return this;
            
            Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName(formatName);
            if (!writers.hasNext()) 
                throw new UnsupportedOperationException(
                        "No suitable ImageWriter found for " + formatName + "."
                );
            
            this.outputFormat = formatName;
            return this;
        

        private String outputFormat(T source, String formatName) throws IOException 
            if (source == null) 
                throw new IllegalArgumentException("The resource being processed is null.");
            
            if (StringUtils.isEmpty(formatName)) 
                formatName = orgForm;
             else if (!Objects.equals(orgForm, formatName)) 
                return formatName;
            
            Iterator<ImageReader> iterReader = ImageIO.getImageReaders(ImageIO.createImageInputStream(source));
            if (null == iterReader || !iterReader.hasNext()) 
                throw new UnsupportedOperationException("The resource being processed is not a picture.");
            
            formatName = iterReader.next().getFormatName();
            Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName(formatName);
            if (!writers.hasNext()) 
                throw new UnsupportedOperationException(
                        "No suitable ImageWriter found for " + formatName + "."
                );
            
            return formatName;
        

        private void write(T source, final ImageOutputStream outputStream) throws IOException 
            if (StringUtils.isEmpty(outputFormat)) 
                throw new IllegalStateException("Output format has not been set.");
            
            Objects.requireNonNull(outputStream, "Could not open OutputStream.");
            BufferedImage srcImage;
            if (source instanceof BufferedImage) 
                srcImage = (BufferedImage) source;
             

 

压缩完:

 

尝试了很多方法,如JDK原生的方式及第三方组件java-image-scaling或thumbnailator都不解决问题。

后来采用阿里的SimpleImage解决。记录一下

SimpleImage github地址:https://github.com/alibaba/simpleimage
依赖jar:commons-io-2.4.jar
commons-lang.jar
commons-logging-1.1.1.jar
jai_codec-1.1.3.jar
jai_core-1.1.3.jar

测试代码:

    public static void main(String[] args) {
        // 原图
        File in = new File(
                "C:/Users/Administrator/Desktop/img/20170916/attachment_15055700026790.jpeg");
        // 目的图
        File out = new File(
                "C:/Users/Administrator/Desktop/img/20170916/attachment_15055700026790a.jpeg");
        FileInputStream input = null;
        FileOutputStream outStream = null;
        WriteRender wr = null;
        try {
            BufferedImage image = ImageIO.read(in);
            ScaleParameter scaleParam = new ScaleParameter(image.getWidth(),
                    image.getHeight()); // 将图像缩略到1024x1024以内,不足1024x1024则不做任何处理
            input = new FileInputStream(in);
            outStream = new FileOutputStream(out);
            ImageRender rr = new ReadRender(input);
            ImageRender sr = new ScaleRender(rr, scaleParam);
            wr = new WriteRender(sr, outStream);

            wr.render(); // 触发图像处理
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (null != input) {
                try {
                    input.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            if (null != outStream) {
                try {
                    outStream.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            if (wr != null) {
                try {
                    wr.dispose(); // 释放simpleImage的内部资源
                } catch (SimpleImageException ignore) {
                    // skip ...
                }
            }
        }
    }

引入的pom文件代码:

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>simpleimage</artifactId>
    <version>1.2.3</version>
</dependency>

 

以上是关于java图片压缩的主要内容,如果未能解决你的问题,请参考以下文章

java如何实现把一个大图片压缩到指定大小的图片且长宽比不变?

java上传图片并压缩图片大小

java如何实现把一个大图片压缩到指定大小的图片且长宽比不变

Java图片缩略图裁剪水印缩放旋转压缩转格式-Thumbnailator图像处理

JPG&PNG图片压缩java实现

Java图片缩略图裁剪水印缩放旋转压缩转格式-Thumbnailator图像处理