图片压缩库 compressor 学习

compressor 是一个 Android 平台上的开源图片压缩库,使用它,可以方便的对本地图片进行压缩,与此同时,该库还提供了各种压缩参数的设置选项。

使用

val compressedImageFile = Compressor.compress(context, actualImageFile) {
    resolution(1280, 720)
    quality(80)
    format(Bitmap.CompressFormat.WEBP)
    size(2_097_152) // 2 MB
}

输入:

  • 一个图片文件
  • 压缩质量,以及格式化类型、最大压缩质量。

输出:

  • 压缩后的图片文件(该文件默认存储应用的沙盒目录下)

核心

下面分析一下这个仓库的核心点。

压缩实现

该库的功能为压缩图片,具体压缩是通过 Bitmap 自身提供的 compress 方法进行压缩。

bitmap.compress(format, quality, fileOutputStream)

压缩参数组合

压缩操作是通过单例类 Compressorcompress方法入口来完成,具体需要先指定目标压缩文件,然后指定压缩参数。

而压缩参数控制是通过第四个参数 compressionPatch 控制,它有一个默认的实现 DefaultConstraint.default(),所以如果不指定其他设置,默认设置就会生效。

此外,当设置了自定义的压缩参数设置,这些参数设置项都会保存在 Compressionconstraints 集合中,这是一个图片压缩参数的抽象接口集合,然后遍历参数集合,并调用不同的压缩参数实现,如下所示,这里是一种链式调用效果:

compression.constraints.forEach { constraint ->
    //该策略是否满足条件
    while (constraint.isSatisfied(result).not()) {
        //如果不满足,就进行处理
        result = constraint.satisfy(result)
    }
}

这样每一个压缩参数的实现结果,都会作为接下来压缩参数的输入,从而达到链式调用的效果,一步一步,让所有的参数设置在一个图片源文件上生效。

压缩参数接口

Constraint 接口是该库的核心,也是一个很巧妙的设计。

通常来讲,对于图片压缩,我们可以按照面向过程的思想,只需要定义一个方法,然后在方法中对图片压缩质量、压缩格式、输出位置等按个进行处理,最终进行压缩,这样代码逻辑就会集中在一块里,这样的设计对后续代码的维护并不好,而且不具备模块性,整个是一个大块,看着也不是很优雅。

该库通过 Constraint 的接口很优雅的解决了这个问题。

不同的压缩参数,自己去实现自己的压缩方案,这个接口提供了两个方法:

 interface Constraint {
    fun isSatisfied(imageFile: File): Boolean
    fun satisfy(imageFile: File): File
}

第一个方法是 isSatisfied,它用于判断当前图片文件是否已经满足参数设置条件,如果已经满足,就不执行 satisfy 方法,否则就执行 satisfy 方法,该方法完成具体的压缩设置操作。

比如 FormatConstraint 的实现,这是指定压缩格式的实现类,如果当前图片已经是指定的格式,就进行处理,否则不处理。

class FormatConstraint(private val format: Bitmap.CompressFormat) : Constraint {
    override fun isSatisfied(imageFile: File): Boolean {
        return format == imageFile.compressFormat()
    }

    override fun satisfy(imageFile: File): File {
        return overWrite(imageFile, loadBitmap(imageFile), format)
    }
}

这里当检测到当前图片的格式不是指定的格式,就会执行 satisfy 方法,satisfy 方法中执行具体的压缩,纵观其他几个参数策略的实现,它们大都是通过 overWriter 去进行具体的图片参数设置。

overWrite 的实现

  • 检查图片格式是否跟指定格式一致,否则更改图片名称后缀
  • 删除临时的本地图片文件
  • 使用新参数对 Bitmap 进行压缩、处理,并保存到新的临时文件并返回

代码如下所示:

fun overWrite(imageFile: File, bitmap: Bitmap, format: Bitmap.CompressFormat = imageFile.compressFormat(), quality: Int = 100): File {
    val result = if (format == imageFile.compressFormat()) {
        imageFile
    } else {
        File("${imageFile.absolutePath.substringBeforeLast(".")}.${format.extension()}")
    }
    imageFile.delete()
    saveBitmap(bitmap, result, format, quality)
    return result
}

saveBitmap 方法具体就是调用 Bitmap 的 compress 方法进行压缩。

拆分

  • Constraint 压缩参数设置的抽象接口,每一种压缩策略必须实现该接口
  • Compressor 压缩门面类,入口类,只提供一个 方法,用于让调用者设置不同的压缩选项,并启动压缩。
    • Compression 一个用于盛放不同 Constraint 的集合
  • 不同压缩策略的实现类
    • DefaultConstraint 默认压缩参数的实现
    • DestinationConstraint 指定压缩文件输出的文件位置
    • FormatConstraint 指定文件最终输出的压缩格式
    • QualityConstraint 指定压缩质量
    • ResolutionConstraint 指定图片宽高值
    • SizeConstraint 指定图片最终的压缩大小

细节

  • 压缩质量设置对 PNG图片无效。

这是由于 Bitmap 自身的压缩限制,它提供的 compress 方法,即使设置了压缩质量,但是对 PNG 格式无效。

from Bitmap#compress 参数介绍

  • 如何实现指定大小的压缩 #SizeConstraint

设置文件最大质量,如果当前文件大小大于最大质量,则继续进行压缩,具体通过设置图片采样率进行压缩,并设置最低采样率为10,另外设置了压缩次数,如果超过了指定的压缩次数,还没有达到大小,则不再压缩,技即使图片质量还没有达到目标值。

不足

从上面 overWrite 方法的实现可以看到,每一次压缩参数的生效,都会伴随上一个缓存文件的删除,以及下一个临时文件的生成,这样可能导致压缩会产生比较多的 IO 开销。

但这是一种博弈,这样的好处,是把不同的压缩参数实现拆分到不同的模块类,让代码结构更清晰,而且在我开发咕咚云图(一个手机图床)的过程中,并没有发现 IO 开销导致什么问题,所以,相比这样设计为代码带来的简洁以及可维护性,这样的 IO 开销可以忽略。

咕咚 DeepSource 2020/12/03

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,214评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,307评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,543评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,221评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,224评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,007评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,313评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,956评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,441评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,925评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,018评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,685评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,234评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,240评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,464评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,467评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,762评论 2 345

推荐阅读更多精彩内容