写在前面
压缩原理,基于工具thumbnailator,其底层也是基于JDK提供的BufferedImage及Graphics2D,不一样的仅仅是它自己的一套算法来根据压缩比例来重新计算图片的长合宽,重新绘制图片。
依赖
<dependency>
<groupId>net.coobird</groupId>
<artifactId>thumbnailator</artifactId>
<version>0.4.20</version>
</dependency>
工具类
/**
* 图片压缩(此方法优化了算法,极大降低压缩时间成本,推荐使用)
* <p>
* 默认压缩目标大小允许的误差范围:0.1
* <p>
* 默认压缩时的图片格式:png
*
* @param img - 待压缩图片
* @param compressSize - 压缩目标大小(单位:kb)
* @return image compress bytes
*/
public static byte[] compress(byte[] img, double compressSize) throws IOException {
return compress(img, compressSize, "png");
}
/**
* 图片压缩(此方法优化了算法,极大降低压缩时间成本,推荐使用)
* <p>
* 默认压缩目标大小允许的误差范围:0.1
*
* @param img - 待压缩图片
* @param compressSize - 压缩目标大小(单位:kb)
* @param suffix - 原文件后缀(必传)
* @return image compress bytes
*/
public static byte[] compress(byte[] img, double compressSize, String suffix) throws IOException {
return compress(img, compressSize, 0.1D, suffix);
}
/**
* 图片压缩(此方法优化了算法,极大降低压缩时间成本,推荐使用)
*
* @param img - 待压缩图片
* @param compressSize - 压缩目标大小(单位:kb)
* @param errorsFactor - 允许与目标值的误差范围因子:范围(0, 1)
* @param suffix - 原文件后缀(必传)
* @return image compress bytes
*/
public static byte[] compress(byte[] img, double compressSize, double errorsFactor, String suffix) throws IOException {
if ((img.length / 1024D) <= compressSize) {
return img;
}
Asserts.isNotEmpty(suffix, "必须指定压缩文件后缀名,如png、jpg等");
Asserts.isTrue(errorsFactor > 0 && errorsFactor < 1, "允许与目标值的误差范围因子需在(0,1)之间,且不包含头尾");
ByteArrayOutputStream cache = new ByteArrayOutputStream();
ByteArrayInputStream src = new ByteArrayInputStream(img);
double min = 0, max = 1;
double scale = (min + max) / 2.0D;
final double targetSize = compressSize * 1024D;
final double minPermitSize = targetSize * (1 - errorsFactor);
final double maxPermitSize = targetSize * (1 + errorsFactor);
Set<Double> prevent = new HashSet<>();// 用于标识无法压缩到目标范围类的图片大小,防止死循环
while (true) {
Thumbnails.of(src).scale(scale).outputFormat(suffix).toOutputStream(cache);
double actualSize = cache.size();
if (!prevent.add(actualSize)) {// 防止死循环
log.info("压缩轮询次数:{}, 压缩率: {}", prevent.size() + 1, scale);
break;
}
if (actualSize >= minPermitSize && actualSize <= maxPermitSize) {
log.info("压缩轮询次数:{}, 压缩率: {}", prevent.size(), scale);
break;
}
if (actualSize >= targetSize) {// 实际压缩率小了
max = scale;
} else {
min = scale;
}
scale = (min + max) / 2.0D;
cache.reset();// 清空当前已读取缓存
src = new ByteArrayInputStream(img);// 重新加载下一次要读取的资源
}
return cache.toByteArray();
}