iOS-图片压缩
移动端开发经常会遇到图片压缩的问题,场景有更新头像,发帖等。今天我们就了解一下
图片格式基础
- 点阵图:也叫位图。用像素为单位,像素保存颜色信息,排列像素实现显示。
- 矢量图:记录元素形状和颜色的算法,显示时展示算法运算的结果。
颜色
表示颜色时,有两种形式,一种为索引色(Index Color),一种为直接色(Direct Color)
- 索引色:用一个数字索引代表一种颜色,在图像信息中存储数字到颜色的映射关系表(调色盘 Palette)。每个像素保存该像素颜色对应的数字索引。一般调色盘只能存储有限种类的颜色,通常为 256 种。所以每个像素的数字占用 1 字节(8 bit)大小。
- 直接色:用四个数字来代表一种颜色,数字分别对应颜色中红色,绿色,蓝色,透明度(RGBA)。每个像素保存这四个纬度的信息来代表该像素的颜色。根据色彩深度(每个像素存储颜色信息的 bit 数不同),最多可以支持的颜色种类也不同,常见的有 8 位(R3+G3+B2)、16 位(R5+G6+B5)、24 位(R8+G8+B8)、32 位(A8+R8+G8+B8)。所以每个像素占用 1~4 字节大小。
移动端常用图片格式
图片格式中一般分为静态图和动态图
静态图
JPG:是支持 JPEG( 一种有损压缩方法)标准中最常用的图片格式。采用点阵图。常见的是使用 24 位的颜色深度的直接色(不支持透明)。
PNG:是支持无损压缩的图片格式。采用点阵图。PNG 有 5 种颜色选项:索引色、灰度、灰度透明、真彩色(24 位直接色)、真彩色透明(32 位直接色)。
WebP:是同时支持有损压缩和无所压缩的的图片格式。采用点阵图。支持 32 位直接色。移动端支持情况如下:
系统 | 原生 | WebView | 浏览器 |
---|---|---|---|
iOS | 第三方库支持 | 不支持 | 支持 |
Android | 4.3后支持完整功能 | 支持 | 支持 |
动态图
- GIF:是支持无损压缩的图片格式。采用点阵图。使用索引色,并有 1 位透明度通道(透明与否)。
- APNG:基于 PNG 格式扩展的格式,加入动态图支持。采用点阵图。使用 32 位直接色。但没有被官方 PNG 接纳。移动端支持情况如下:
系统 | 原生 | WebView | 浏览器 |
---|---|---|---|
iOS | 支持 | 支持 | 支持 |
Android | 第三方库支持 | 不支持 | 不支持 |
- Animated Webp:Webp 的动图形式,实际上是文件中打包了多个单帧 Webp,在 libwebp 0.4 后开始支持。移动端支持情况如下:
系统 | 原生 | WebView | 浏览器 |
---|---|---|---|
iOS | 第三方库支持 | 不支持 | 不支持 |
Android | 第三方库支持 | 不支持 | 不支持 |
而由于一般项目需要兼容三端(iOS、Android、Web 的关系),最简单就是支持 JPG、PNG、GIF 这三种通用的格式。所以本文暂不讨论其余图片格式的压缩。
移动端系统图片处理架构
根据我的了解,画了一下 iOS&Android 图片处理架构。iOS 这边,也是可以直接调用底层一点的框架的。
iOS 的 ImageIO
本文 iOS 端处理图片主要用 ImageIO 框架,使用的原因主要是静态图动态图 API 调用保持一致,且不会因为 UIImage 转换时会丢失一部分数据的信息。
ImageIO 主要提供了图片编解码功能,封装了一套 C 语言接口。在 Swift 中不需要对 C 对象进行内存管理,会比 Objective-C 中使用方便不少,但 api 结果返回都是 Optional(实际上非空),需要用 guard/if
,或者 !进行转换。
解码
1. 创建 CGImageSourceCGImageSource
相当于 ImageIO 数据来源的抽象类。通用的使用方式 CGImageSourceCreateWithDataProvider:
需要提供一个 DataProvider,可以指定文件、URL、Data 等输入。也有通过传入 CFData 来进行创建的便捷方法 CGImageSourceCreateWithData:
。方法的第二个参数 options 传入一个字典进行配置。根据 Apple 在 WWDC 2018 上的 Image and Graphics Best Practices 上的例子,当不需要解码仅需要创建 CGImageSource
的时候,应该将 kCGImageSourceShouldCache
设为 false。
2. 解码得到 CGImage
用 CGImageSourceCreateImageAtIndex:
或者 CGImageSourceCreateThumbnailAtIndex:
来获取生成的 CGImage
,这里参数的 Index 就是第几帧图片,静态图传入 0 即可。
编码
1. 创建 CGImageDestinationCGImageDestination
相当于ImageIO
数据输出的抽象类。通用的使用方式 CGImageDestinationCreateWithDataConsumer:
需要提供一个 DataConsumer
,可以置顶 URL、Data 等输入。也有通过传入 CFData 来进行创建的便捷方法 CGImageDestinationCreateWithData:
,输出会写入到传入的Data
中。方法还需要提供图片类型,图片帧数。
2. 添加 CGImage
添加 CGImage
使用CGImageDestinationAddImage:
方法,动图的话,按顺序多次调用就行了。
而且还有一个特别的 CGImageDestinationAddImageFromSource:
方法,添加的其实是一个 CGImageSource
,有什么用呢,通过 options
参数,达到改变图像设置的作用。比如改变 JPG 的压缩参数,用上这个功能后,就不需要转换成更顶层的对象(比如 UIImage
),减少了转换时的编解码的损耗,达到性能更优的目的。
3. 进行编码
调用 CGImageDestinationFinalize:
,表示开始编码,完成后会返回一个Bool
值,并将数据写入 CGImageDestination
提供的 DataConsumer 中。
压缩思路分析
位图占用的空间大小,其实就是像素数量 x 单像素占用空间 x 帧数。所以减小图片空间大小,其实就从这三个方向下手。其中单像素占用空间,在直接色的情况下,主要和色彩深度相关。在实际项目中,改变色彩深度会导致图片颜色和原图没有保持完全一致,笔者并不建议对色彩深度进行更改。而像素数量就是平时非常常用的图片分辨率缩放。除此之外,JPG 格式还有特有的通过指定压缩系数来进行有损压缩。
JPG:压缩系数 + 分辨率缩放 + 色彩深度降低
PNG: 分辨率缩放 + 降低色彩深度
GIF:减少帧数 + 每帧分辨率缩放 + 减小调色盘
目的
减少图片占用内存空间
质量压缩
图片的质量压缩 降低图片质量(大小)。
原理
通过算法扣掉(同化)了 图片中的一些某个点附近相近的像素,达到降低质量 减少 文件大小的目的。
他其实只能 实现对 file 的影响,对加载这个图片出来的bitmap 内存是无法节省的 ,还是那么大。 因为 bitmap 在内存中的大小是按照 像素 计算的 ,也就是width * height ,对于质量压缩,并不会改变图片的真实的像素(像素大小不会变)。
尺寸压缩
就是按照一定的倍数对图片减少单位尺寸的像素值。
原理
通过减少单位尺寸的像素值,真正意义上的降低像素值。