当前位置:   article > 正文

iOS代码质量要求_图片压缩(iOS)

swift tinypng

cf0f21f5fb2831f45bdbf9375adbf376.png

场景很简单,上传图片前压缩图片,节省流量和发图时间。最近看了看 iOS 的静态图片压缩,这里记个笔记。本人之前没学过 iOS 和 Swift,本文是一篇入门文章,描述不到位之处请大家多多批评斧正。 ̄ω ̄=

从这几个方向入手:

  1. 选择图片处理库
  2. 减小图片尺寸
  3. 降低图片质量
  4. 改变图片格式
  5. 降低像素中每个 color component 的 depth

选择图片处理库

我把它放在本文的第一位,因为接下来的代码展示都要用到它。

我选择的是 iOS 自带的 Image I/O,好处是速度快、内存占用小、主流图片格式硬解,Image and Graphics Best Practices - WWDC 2018 和 Image Resizing Techniques 中有介绍。

减小图片尺寸

根据原图的尺寸推测出预期缩略图的尺寸,这个与业务场景强相关,开源的实现比如 Luban,是模仿微信的缩略图尺寸计算逻辑。

首先获取原图尺寸:

let 

然后根据原图尺寸区分几种情况,这里给个 Example,真正用的话可以根据 Luban 改改:

  • 短边与长边比例小于1/3的被算作长图,单独开一条分支。(比例小于 x/x 被算作超长图)
  • 正常比例的图
    • 尺寸较小的,截图等:可视最长边长度,尺寸等比缩小到长边为 1280
    • 尺寸较大的,照片等:可视最短边长度,尺寸等比缩小到短边为 1080 或 1280

计算出预期缩略图的尺寸(thumbWidth, thumbHeight)之后,创建缩略图 CGImage:

let 

快速看了下选项 kCGImageSourceSubsampleFactor 的注释,它能令解码后返回的图片的长宽缩小为原图的 1/n,n ∈ { 2, 4, 8 }。

有点像 libjpeg 支持的 IDCT Scaling,即 jpeg_decompress_struct 里的 scale_num / scale_denom。Skia(Android 用的就是它)的 SkJpegCodec 有提到,libjpeg-turbo 目前对其中的 1/4 和 1/2 有 SIMD 加速。

咳咳,跑远了,要注意一点,用 JPEG 的 IDCT scaling 虽然快,但会导致图像质量变差,我并没有尝试 kCGImageSourceSubsampleFactor 这个选项,不确定它会不会令图像质量变差,有兴趣的同学可以试试。 // TODO 之后补上

就顺便一提,如果想获得微信的缩略图尺寸计算逻辑,可以先从日志入手,自己发几张图,从微信打的日志定位到具体的业务代码。微信日志是非对称加密的。一种方法是修改 xlog 库,拿一台 root 的 Android 手机,把 libwechatxlog.so pull 出来,修改成忽略公钥,再把它 push 回去,替换掉所有 libwechatxlog.so 出现的地方,这样就得到了一个日志不加密的微信,之后 jadx 打开,在代码里搜索相关日志即可。另一种方法是在 Java 层加 hook,不提。

降低图片质量

这节主要是针对 JPEG 格式写的,JPEG 在编码时可以选择质量,iOS (AppleJPEG) 可以指定 0..1.0,而 libjpeg 是 0..100,不要以为它俩之间就是 1:100 的关系,实际我用工具 identify 估算了一下,iOS (AppleJPEG) 的 0.38 约对应于 libjpeg 的 60,0.44 约对应于 70。

这里我们选择 0.38 作为质量参数:

var 

实际场景中,这个值可以受下发的配置和网络状态等环境变量影响,比如我 WIFI 下指定 0.44,4G 下指定 0.38。

改变图片格式

  • 如果想做得简单点,编码格式就两种:JPEG 和 PNG。无透明色选 JPEG,有透明色选 PNG。因为 PNG 格式是无损压缩,一般比 JPEG 格式的图片大,也要更清晰。

透明色由 alpha channel 决定。一般 alpha channel 存在每一个像素里,当然还有其他结构的存储方式,比如 Indexed PNG 会把它写到独立的 tRNS 段。判断图片是否包含 alpha channel 可以用 CGImage Source:

let 

或 CGImage 的 alphaInfo 字段:

let 

来判断。这里提一下 CGImage 的 alphaInfo 字段:

public 

其他字段没啥好说的,premultipliedLast 和 premultipliedFirst 代表“预乘 Alpha”,可以让图片的加载消耗变少,一般在游戏等 CPU (GPU) 密集的场景有用,介绍见 Premultiplied alpha。

压缩成 JPEG 还是 PNG 也就一个参数的区别:

if 

另外我发现还有一个参数 kUTTypeJPEG2000,这个新的 JPEG 格式支持透明色,但推广不起来(估计是不向前兼容的原因)。

以下手段本人还在研究,到时来填坑!!!

  • 如果想做得好点复杂点,比如上传的图片全部编码成 WEBP 或者更好的 HEIF(没错它比 WEBP 还小),那可能需要客户端自己引入新的编码库,还需要服务端支持转码成 JPEG 或 PNG,以便用户下载编辑。好在 HEIF 那么出名,已经有人给我们准备好库了:libheif-rs(突然放出 Rust crate

在 iOS 11 或以上,照片格式已经是 HEIF,iOS 11 或以上也已经支持 HEIF 格式的编解码:

if 

在本文的有损压缩的场景下,HEIF 跟 JPEG 相比,虽然编码耗时长,但输出图片的体积会小很多,网上有许多比较,不提。

由于 HEIF 比同等体积的 JPEG 更为清晰,质量参数可以给大点。

  • 如果想把握未来,可以研究研究同事给我墙裂推荐的 AV1,这坑不填。

降低像素中每个 color component 的 depth

比如 iPhone XS Max 的截屏图片的 color depth 都是 16-bit,降到 8-bit 图片体积会小很多。

let 

多说几句

  1. 很多图片会存一些“元信息”、“元数据”,像拍摄的地理位置、时间、thumbnail 等等,但压缩图片场景需要的一般就两个:color profile 和 orientation。color profile 防止偏色,iPhone 产出的图片一般带有 Display P3,是要写到压缩后的图片里的,这个 Image I/O 会默认自动 copy;指定了 kCGImageSourceCreateThumbnailWithTransform 也会帮你做旋转对称。而大部分多余的信息和数据(地理位置、时间、thumbnail)会被去掉。这块基本不用担心。
  2. 处理图片要小心内存不够用 didReceiveMemoryWarning。
  3. 好的采样算法(比如 Lanczos)会带来更好的图像质量,但一般会消耗会更多的 CPU。目前看来没啥必要。
  4. 要怎么知道 JPEG 图片的质量?很遗憾,质量并不是元信息,要猜测 JPEG 图片质量的话可以参考 ImageMagick。
  5. 要避免重复压缩。加上个预期的缩略图的大小。
if

6. PNG 有没有有损压缩?有,但是不适合本文的场景。TinyPNG 就是有损压缩,开源的有 https://github.com/kornelski/pngquant,思想都是 quantization + indexed。完全可以用 pngquant 自己撸一个不输于 TinyPNG。注意因为出来的图是 indexed,编码时记得 NoFilter。

7. 已经很久很久没有写过博客了。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/AllinToyou/article/detail/140566
推荐阅读
相关标签
  

闽ICP备14008679号