本项目主要基于 Android 自带的图片压缩 API 进行实现,提供了开源压缩方案 Luban 和 Compressor 的实现,提供了 Fie 类型数据源的方式,并在它们的基础之上进行了功能上的拓展。该项目的主要目的在于:提供一个统一图片压缩框库的实现,集成常用的两种图片压缩算法,让你以更低的成本集成图片压缩功能到自己的项目中。
项目背景:开源的图片压缩库
现在 Github 上的图片压缩框架主要有 Luban 和 Compressor 两个。Star 的数量也比较高,一个 11K,另一个 4K. 但是,这两个图片压缩的库有各自的优点和缺点。下面我们通过一个表格总结一下:
框架 | 优点 | 缺点 |
---|---|---|
Luban | 据说是根据微信图片压缩逆推的算法。 | 1.只适用于一般的图片展示的场景,无法对图片的尺寸进行精准压缩;2.压缩策略单一,应用场景有限。 |
Compressor | 1.可以对图片的尺寸进行精准压缩;2.支持同步或异步获取。 | 1.无法通过最佳采样量压缩图片,手动设置无法保证体积最小,质量无法保证。 |
以上,我们总结了 Github 上面的两款比较受欢迎的开源图片压缩库的优缺点。正如我们上面所说,我们希望能够综合它们的优点,并且解决以上两款开源库设计不好的地方。因此,你可以通过下文来了解我们的库所提供的功能,以及如何在您的项目中接入我们的压缩方案。
功能和特性
目前,我们的库已经支持了下面的功能,在后面的介绍中,我们会介绍如何在项目中进行详细配置和使用:
- 支持 Luban 压缩方案:据介绍这是微信逆推的压缩算法,它在我们的项目中只作为一种可选的压缩方案,除此之外您还可以使用 Compressor 进行压缩。
- 支持 Compressor 压缩方案:这种压缩方案综合了 Android 自带的三种压缩方式,可以对压缩结果的尺寸进行精确的控制。此外,在我们的项目中,我们对这种压缩方案的功能进行了拓展,还提供了多种可选的策略,用来对尺寸进行更详细的配置。
- 支持 AsyncTask + 回调的压缩方式:这种方式通过使用 AsyncTask 在后台线程中执行压缩任务,当获取到压缩结果的时候通过 Handler 在主线程中返回压缩结果。
- 支持 Kotlin 协程:引入了 Kotlin 协程,你可以在这个版本上面使用 Kotlin 协程进行压缩并获取结果。
提供了同步的压缩方式:当然,有时候我们并不需要使用回调或者 RxJava 执行异步任务。比如,当我们本身已经处于后台线程的时候,我们希望的只是在当前线程中直接执行压缩任务并拿到压缩结果。因此,为了让我们的库适用于更多的应用场景,我们提出了这种压缩方案。
鲁班压缩算法
目前做App开发总绕不开图片这个元素。但是随着手机拍照分辨率的提升,图片的压缩成为一个很重要的问题。单纯对图片进行裁切,压缩已经有很多文章介绍。但是裁切成多少,压缩成多少却很难控制好,裁切过头图片太小,质量压缩过头则显示效果太差。
于是自然想到App巨头“微信”会是怎么处理,Luban(鲁班)就是通过在微信朋友圈发送近100张不同分辨率图片,对比原图与微信压缩后的图片逆向推算出来的压缩算法。
1、判断图片比例值,是否处于以下区间内;
- [1, 0.5625) 即图片处于 [1:1 ~ 9:16) 比例范围内
- [0.5625, 0.5) 即图片处于 [9:16 ~ 1:2) 比例范围内
- [0.5, 0) 即图片处于 [1:2 ~ 1:∞) 比例范围内
2、判断图片最长边是否过边界值;
- [1, 0.5625) 边界值为:1664 * n(n=1), 4990 * n(n=2), 1280 * pow(2, n-1)(n≥3)
- [0.5625, 0.5) 边界值为:1280 * pow(2, n-1)(n≥1)
- [0.5, 0) 边界值为:1280 * pow(2, n-1)(n≥1)
3、计算压缩图片实际边长值,以第2步计算结果为准,超过某个边界值则:width / pow(2, n-1),height/pow(2, n-1)
4、计算压缩图片的实际文件大小,以第2、3步结果为准,图片比例越大则文件越大。 size = (newW * newH) / (width * height) * m;
- [1, 0.5625) 则 width & height 对应 1664,4990,1280 * n(n≥3),m 对应 150,300,300;
- [0.5625, 0.5) 则 width = 1440,height = 2560, m = 200;
- [0.5, 0) 则 width = 1280,height = 1280 / scale,m = 500;注:scale为比例值
5、判断第4步的size是否过小
- [1, 0.5625) 则最小 size 对应 60,60,100
- [0.5625, 0.5) 则最小 size 都为 100
- [0.5, 0) 则最小 size 都为 100
6、将前面求到的值压缩图片 width, height, size 传入压缩流程,压缩图片直到满足以上数值
实践对比
为了比较扩展库的压缩功能是否正常,进行了10张网络上收集而来的图片(均大于1.5M),使用扩展库和 Compressor 和 Luban库进行对比,使用的压缩质量均为60(Luban默认为60,且无法更改)。
图片/原始大小 | compressor | luban | 扩展库 compressor 模式 | 扩展库 luban 模式 |
---|---|---|---|---|
p1/3.6M | 116.06K | 115.95K | 116.06K | 115.95K |
p2/4.11M | 71.74K | 39.68K | 71.74K | 39.68K |
p3/8.63M | 18.32K | 18.14K | 18.32K | 18.14K |
p4/3.84M | 160.44K | 161.08K | 160.44K | 161.08K |
p5/5.64M | 171.26K | 171.02K | 171.26K | 171.02K |
p6/7.32M | 163.2K | 163.43K | 163.2K | 163.43K |
p7/1.88M | 17.14K | 17.14K | 17.14K | 17.14K |
p8/3.89M | 96.68K | 56.37K | 96.68K | 56.37K |
p9/1.16M | 113.26K | 66.69K | 113.26K | 66.69K |
p10/4.31M | 278.76K | 278.05K | 278.76K | 278.05K |
由此可见,压缩效果是一致的。
使用
1、在项目的依赖中添加该库的依赖:
implementation 'com.mmc.plug:compressor:1.0.0-SNAPSHOT'
2、使用我们库进行压缩。 协程用法:
- 1、compressor模式。
GlobalScope.launch(Dispatchers.Main) {
val compressedImageFile = Compressor.compress(context, actualImageFile) {
destination(File("${cachePath(context)}"))
resolution(1280, 720)
quality(60)
format(Bitmap.CompressFormat.JPEG)
size(2_097_152) // 2 MB
}
}
- 2、luban模式。
GlobalScope.launch(Dispatchers.Main) {
val compressedImageFile = Compressor.compress(context, actualImageFile) {
destination(File("${cachePath(context)}"))
luban(ignoreBySize)
}
}
同步用法:
Compress.with(context, actualImageFile)
.setDestination(File("${cachePath(context)}"))
.setResolution(1280, 720)
.setQuality(60)
.setFormat(Bitmap.CompressFormat.JPEG)
.setSize(2_097_152) // 2 MB
.get()
异步回调用法:
Compress.with(context, actualImageFile)
.setDestination(File("${cachePath(context)}"))
.setLubanSize(ignoreBySize)
.setCompressListener(object : CompressListener {
override fun onSuccess(compressedImageFile: File?) {
}
override fun onError(throwable: Throwable?) {
}
override fun onStart() {
}
}).launch()
扩展库已经上传Maven,有需要的请自取。