- 作者:彭老师
- 日期:2019-06-23
- 类型:Android
- 说明:本文源于 彭老师 手写摘要,如需转载请带上链接或注明出处!
前言
图片操作几乎每个项目都要用到,而图片压缩技术的真正掌握却又少之又少!
有感而发,同样也希望这样的技术能被更多的求学者发现和传播。
在这里 彭老师 发下学习的 视频链接:
安卓黑科技:图片终极压缩,支持自定义配置、不失真和批量处理
在我们的业务场景中,需要使用客户端采集图片,上传服务器,然后对图片信息进行识别。为了提升程序的性能,我们需要保证图片上传服务器的速度的同时,保证用于识别图片的质量。整个优化包括两个方面的内容:
1、相机拍照的优化:包括相机参数的选择、预览、启动速度和照片质量等;
2、图片压缩的优化:基于拍摄的图片和从相册中选择的图片进行压缩,控制图片大小和尺寸。
在本文中,我们主要介绍图片压缩优化,后续我们会介绍如何对 Android 的相机进行封装和优化。本项目主要基于 Android 自带的图片压缩 API 进行封装,结合了 Luban
和 Compressor
的优点,同时提供了用户自定义压缩策略的接口。
该项目的主要目的在于,统一图片压缩框库的实现,集成常用的两种图片压缩算法,让你以更低的成本集成图片压缩功能到自己的项目中。
1、图片压缩的基础知识
对于一般业务场景,当我们展示图片的时候,Glide
会帮我们处理加载的图片的尺寸问题。但在把采集来的图片上传到服务器之前,为了节省流量,我们需要对图片进行压缩。
在 Android 平台上,默认提供的压缩有三种方式:质量压缩和两种尺寸压缩,邻近采样以及双线性采样。下面我们简单介绍下者三种压缩方式都是如何使用的:
####1.1 质量压缩
所谓的质量压缩就是下面的这行代码,它是 Bitmap
的方法。当我们得到了 Bitmap
的时候,即可使用这个方法来实现质量压缩。它一般位于我们所有压缩方法的最后一步。
// android.graphics。Bitmap |
- 该方法接受三个参数,其含义分别如下:
- 1、format:枚举,有三个选项 JPEG, PNG 和 WEBP,表示图片的格式;
- 2、quality:图片的质量,取值在 [0,100] 之间,表示图片质量,越大,图片的质量越高;
- 3、stream:一个输出流,通常是我们压缩结果输出的文件的流
1.2 邻近采样
邻近采样基于临近点插值算法,用像素代替周围的像素。
邻近采样的核心代码只有下面三行,
BitmapFactory.Options options = new BitmapFactory.Options(); |
邻近采样核心的地方在于 inSampleSize 的计算。它通常是我们使用的压缩算法的第一步。
我们可以通过设置 inSampleSize 来得到原始图片采样之后的结果,而不是将原始的图片全部加载到内存中,以防止 OOM。
标准使用姿势如下:
// 获取原始图片的尺寸 |
这里主要分成两个步骤,它们各自的含义是:
1、先通过设置 Options
的 inJustDecodeBounds
为 true
,来加载图片,以得到图片的尺寸信息。此时图片不会被加载到内存中,所以不会造成 OOM,同时我们可以通过 Options
得到原图的尺寸信息。
2、根据上一步中得到的图片的尺寸信息,计算一个 inSampleSize
,然后将 inJustDecodeBounds
设置为 false,以加载采样之后的图片到内存中。
关于 inSampleSize
需要简单说明一下:
inSampleSize 代表压缩后的图像一个像素点代表了原来的几个像素点,例如 inSampleSize
为 4,则压缩后的图像的宽高是原来的 1/4,像素点数是原来的 1/16,inSampleSize
一般会选择 2 的指数,如果不是 2 的指数,内部计算的时候也会向 2 的指数靠近。
所以,实际使用过程中,我们会通过明确指定 inSampleSize
为 2 的指数,来避免内部计算导致的不确定性。
1.3 双线性采样
邻近采样可以对图片的尺寸进行有效的控制,但是它存在几个问题。比如,当我需要把图片的宽度压缩到 1200 左右的时候,如果原始的图片的宽度压是 3200,那么我只能通过设置 inSampleSize
将采样率设置为 2 来将其压缩到 1600. 此时图片的尺寸比我们的要求要大。
就是说,邻近采样无法对图片的尺寸进行更加精准的控制。如果需要对图片尺寸进行更加精准的控制,那么就需要使用双线性压缩了。
双线性采样采用双线性插值算法,相比邻近采样简单粗暴的选择一个像素点代替其他像素点,双线性采样参考源像素相应位置周围 2x2 个点的值,根据相对位置取对应的权重,经过计算得到目标图像。
它在 Android 中的使用也比较简单,
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.blue_red); |
也就是对得到的 Bitmap
应用 createBitmap()
进行处理,并传入 Matrix
指定图片尺寸放缩的比例。该方法返回的 Bitmap
就是双线性压缩之后的结果。
1.4 图片压缩算法总结
在实际使用过程中,我们通常会结合三种压缩方式使用,一般使用的步骤如下,
1、使用邻近采样对原始的图片进行采样,将图片控制到比目标尺寸稍大的大小,防止 OOM;
2、使用双线性采样对图片的尺寸进行压缩,控制图片的尺寸为目标的大小;
3、对上述两个步骤之后得到的图片 Bitmap 进行质量压缩,并将其输出到磁盘上。
当然,本质上 Android 图片的编码是由 Skia 库来完成的…
所以,除了使用 Android 自带的库进行压缩,我们还可以调用外部的库进行压缩。为了追求更高的压缩效率,通常我们会在 Native 层对图片进行处理,这将涉及 JNI 的知识。
Github 上的开源的图片压缩库
现在 Github 上的图片压缩框架主要有 Luban 和 Compressor 两个。Star 的数量也比较高,一个 10K,另一个 4K. 但是,这两个图片压缩的库有各自的优点和缺点。下面我们通过一个表格总结一下:
框架 | 优点 | 缺点 |
---|---|---|
Luban | 据说是根据微信图片压缩逆推的算法 | 1、只适用于一般的图片展示的场景,无法对图片的尺寸进行精准压缩;2、内部封装 AsyncTaks 来进行异步的图片压缩,对于 RxJava 支持不好 |
Compressor | 1、可以对图片的尺寸进行压缩;2、支持 RxJava | 1、尺寸压缩的场景有限,如果有特别的需求,则需要手动修改源代码;2、图片压缩采样的时候计算有问题,导致采样后的图片尺寸总是小于我们指定的尺寸 |
上面的图表已经总结得很详细了。所以,根据上面的两个库各自的优缺点,我们打算开发一个新的图片压缩框架。它满足下面的功能:
1、支持 RxJava:我们可以像使用 Compressor 的时候那样,指定图片压缩的线程和结果监听的线程
2、支持 Luban 压缩算法:Luban 压缩算法核心的部分只在于 inSampleSize 的计算
3、支持 Compressor 压缩算法同时指定更多的参数:Compressor 压缩算法就是我们上述提到的三种压缩算法的总和。
4、提供用户自定义压缩算法的接口:我们希望设计的库可以允许用户自定义压缩策略。
总结:
我们的项目中,需要把图片的短边控制到 1200,长变只适应,只通过改变 Luban 来改变采样率只能把边长控制到一个范围中,无法精准压缩。
所以,我们想到了 Compressor,并提出了 SCALE_SMALLER 的压缩模式. 但是 Luban 也不是用不到,一般用来展示的图片的压缩,它用起来更加方便。
因此,我们在库中综合了两个框架,其实代码量并不大。当然,为了让我们的库功能更加丰富,因此我们提出了自定义压缩策略的接口,也是用来降低压缩策略的更换成本吧。