Java图片压缩工具

工具类使用场景

  1. 公司做人脸识别项目时候需要上传学生、家长、教师、访客的正面照图片,但是人脸识别机器有限制只接收200KB-1M的图片,所以必须做图片压缩到指定范围大少。
  2. APP上传使用产品的评价附件图片,手机直接拍照上传的图片过大,直接存储导致文件服务器存储容量递减,所以要压缩到指定范围大少。

注意事项

  1. 最大递归压缩深度默认为20,一般情况下JVM支持1000~2000的栈,所以请勿设置过大的递归深度,否则会抛出:stackoverflowerror.
  2. 质量比与尺寸比默认均为0.85去压缩,压缩出来的图片少于期待范围时候,会自动递增质量比与尺寸比去压缩。
  3. 图片压缩比较耗时和性能,能异步的话建议异步处理来消峰。本机压缩6M图片到600KB时候耗时3秒。

依赖关系

<dependency>
        <groupId>net.coobird</groupId>
        <artifactId>thumbnailator</artifactId>
        <version>0.4.14</version>
    </dependency>
    
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.29</version>
    </dependency>
    
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.12</version>
        <scope>provided</scope>
    </dependency>

核心代码

1.压缩类

import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import net.coobird.thumbnailator.Thumbnails;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;

/**
 * @author huangrusheng
 * @version 1.0
 * @date 2021/5/20 15:27
 */
@Slf4j
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public final class ImageCompressUtil {
    /**
     * @param imageSource 图片源
     * @param outputStream 压缩后的图片输出地
     * @return
     * @throws IOException
     */
    public static boolean compress(ImageSource imageSource, OutputStream outputStream) throws IOException{
        boolean result = compress(imageSource);
        if(result){
            outputStream.write(imageSource.getSource());
        }
        return result;
    }

    public static boolean compress(ImageSource imageSource) throws IOException {
        if(imageSource.getNumberOfCompressions() == 0){
            imageSource.setStartTime(System.currentTimeMillis());
            imageSource.setOriginalSource(imageSource.getSource());
        }
        if( imageSource.getSource().length>=imageSource.getMinCompressToByteSize()
                &&   imageSource.getSource().length <= imageSource.getMaxCompressToByteSize()) {
               log.info("成功完成图片压缩,总耗时:{}秒 , 压缩次数:{} , 压缩前大少:{} KB ,压缩后大少:{}KB.",
                       (System.currentTimeMillis() - imageSource.getStartTime()) / 1000, imageSource.getNumberOfCompressions(),
                         imageSource.getByteSizeBeforeCompress() / 1024, imageSource.getSource().length / 1024);
            return true;
        }
        if(imageSource.getNumberOfCompressions() > imageSource.getRecursionDepth()){
            log.warn("图片过大,导致递归压缩次数超出:{},请尝试上传其他较小的图片.",imageSource.getNumberOfCompressions());
            return false;
        }
        //如果压缩小于期待值减少,增加下质量与尺寸比例
        if(imageSource.getSource().length < imageSource.getMinCompressToByteSize()){
            log.warn("压缩过度重新压缩,总耗时:{}秒 , 压缩次数:{} , 压缩前大少:{} KB ,压缩后大少:{}KB.",
                    (System.currentTimeMillis()-imageSource.getStartTime())/1000   ,imageSource.getNumberOfCompressions(),
                    imageSource.getByteSizeBeforeCompress()/1024, imageSource.getSource().length/1024 );
            imageSource.setScale(imageSource.getScale() + imageSource.getIncrementCompressQuality());
            imageSource.setQuality(imageSource.getQuality() + imageSource.getIncrementCompressQuality());
            imageSource.setSource(imageSource.getOriginalSource());
        }
        ByteArrayOutputStream byteArrayOutputStream =
                new ByteArrayOutputStream(imageSource.getMaxCompressToByteSize());
        Thumbnails.of(new ByteArrayInputStream(imageSource.getSource()))
                .scale(imageSource.getScale())
                .outputQuality(imageSource.getQuality())
                .outputFormat(imageSource.getImageFormat())
                .toOutputStream(byteArrayOutputStream);
        imageSource.setSource(byteArrayOutputStream.toByteArray());
        imageSource.increaseNumberOfCompressions();
        return compress(imageSource);
    }
    
}

2.压缩参数POJO类

import lombok.Data;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
/**
 * @author huangrusheng
 * @version 1.0
 * @date 2021/5/20 15:16
 */
@Data
public class ImageSource {
    /**
     * 需压缩的图片,转为字节
     */
    private  byte[] source;
    /**
     * 记录压缩前的图片,转为字节
     */
    private  byte[] originalSource;
    /**
     * 当前图片压缩的次数
     */
    private int  numberOfCompressions = 0;
    private long startTime ;
    /**
     * 未执行压缩前,图片的字节大少
     */
    private int byteSizeBeforeCompress ;
    /**
     * 压缩质量
     */
    private double quality = 0.85;
    /**
     * 压缩尺寸比例
     */
    private double scale = 0.85;
    private double incrementCompressQuality = 0.05;
    /**
     * 需压缩到最大容许的字节数,默认为1M
     */
    private int maxCompressToByteSize = 1024*1024*1;
    /**
     * 需压缩到最小容许的字节数,默认为200KB
     */
    private long minCompressToByteSize = 1024*200;
    /**
     * 压缩后的图片格式,默认JPG
     */
    private  String imageFormat = "jpg";
    /**
     * 最大递归压缩深度
     */
    private  int  recursionDepth = 20;

    public ImageSource(byte[] source){
        this.initSource(source);
    }

    public ImageSource(InputStream inputStream) throws IOException {
        this(inputStream,null);
    }

    public ImageSource(InputStream inputStream,Long inputByteSize ) throws IOException{
        ByteArrayOutputStream byteArrayOutputStream
                = new ByteArrayOutputStream(inputByteSize == null ? 2 * 1024 * 1024 : inputByteSize.intValue());
        int n ;
        byte [] buffer = new byte[1024 * 100];
        while (  -1 != ( n = inputStream.read(buffer))){
            byteArrayOutputStream.write(buffer,0,n);
        }
        this.initSource(byteArrayOutputStream.toByteArray());
    }


    private void initSource(byte[] source){
        this.source = source;
        this.byteSizeBeforeCompress = this.source.length;
    }

    public void  increaseNumberOfCompressions(){
        this.numberOfCompressions = this.numberOfCompressions + 1;
    }
}

单元测试用例

import cn.hutool.core.io.IoUtil;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import java.io.FileInputStream;
import java.io.FileOutputStream;
/**
 * @author huangrusheng
 * @version 1.0
 * @date 2021/5/20 16:05
 */
@RunWith(JUnit4.class)
public class ImageCompressTest {

    @Test
    public void testCompress() throws Exception{
        FileInputStream fileInputStream = null ;
        FileOutputStream fileOutputStream = null;
        try {
            fileInputStream = new FileInputStream("D:\\image_source.jpg");
            fileOutputStream = new FileOutputStream("D:\\image_compress.jpg");
            ImageSource imageSource = new ImageSource(fileInputStream);
            imageSource.setMinCompressToByteSize(300*1024);
            imageSource.setMaxCompressToByteSize(800*1024);
            imageSource.setImageFormat("jpg");
            ImageCompressUtil.compress(imageSource,fileOutputStream);
        }finally {
            IoUtil.close(fileInputStream);
            IoUtil.flush(fileOutputStream);
            IoUtil.close(fileOutputStream);
        }
    }
}

java 压缩图片 透明 java图片压缩工具_Source

压缩前

java 压缩图片 透明 java图片压缩工具_java_02


压缩后

java 压缩图片 透明 java图片压缩工具_Source_03