文章目录
  1. 1. 图片渲染的编码原理
    1. 1.1. 图片渲染管线
    2. 1.2. 缓冲区
    3. 1.3. 解码(Decoding)
    4. 1.4. 图片来源(Image Sources)
    5. 1.5. 自定义绘制(Custom Drawing)
    6. 1.6. 离屏绘制(Drawing Off-Screen)
  2. 2. 相关资料

之前在浏览简书技术博客的时候,看到了一篇关于图片的底层存储技术的博文,然后引发了对于图片处理方面的兴趣,就大量查阅和研读了关于iOS端的图片处理技术相关的技术文档和一些比较有意思的技术博文,然后准备系统地将图片处理这个比较常用、比较大的技术点研究透彻一些.

之前转载过一篇关于图片IO的技术博,今天就来研讨一下关于图片处理的另一个方面–图片压缩技术.

图片渲染的编码原理

<font/ size=3>(WWDC2018 关于图片渲染的Session:WWDC2018 Image and Graphics Best Practices)

图片渲染管线

iOS的框架是基于MVC的架构设计和实现的,所以MVC的思维模式深入到了方方面面,包括图像渲染的设计思路.而从MVC架构来讲,UIImage代表了Model,而UIImageView则代表了View,所以渲染的过程就可以如下图这样表示:

sample1

Image负责图片数据的加载,而ImageView则负责将图片数据渲染到屏幕上.但是~实际上这么区分的话,有一个非常重要的部分被忽略掉了,那就是图片数据的解码过程:

sample2

而图片处理中相当耗费计算资源,会出现资源瓶颈的地方也就是解码这个地方,而为了更加清晰的了解关于图片解码的原理,我们先来了解一下计算机中Buffer这个概念,然后一步一步地深入.

缓冲区

Buffer这个概念在计算机领域通常被定义为一段连续的内存,作为表达某种数据元素的队列来使用,可以简单理解为内存中用来表示同一对象储存内容的意思.而在图像处理领域,主要需要了解一下几种Buffer:

Image Buffers代表了图像(Image)在内存中的表示.每个元素代表一个像素点的颜色,Buffer大小与图像的大小成正比.

sample3

The Frame Buffers代表了图像中一帧在内存中的表示.

sample4

Data Buffers代表了图像的真实文件(ImageFile)在内存中的表示.这个才是真正的图片元数据,也就是表示的是图片真实的Byte数据.而不同的图片有不同的编码格式来储存.Data Buffers并不像ImageBuffer,它并不直接描述像素点的内容,而是和其他文件一样,都是以相应编码格式编码后的Byte表示的.而图像渲染的重点步骤Decode这一流程的操作就是将DataBuffer转换为真正的代表像素点的ImageBuffer.

sample5

所以图像渲染管线的流程实际上是UIImage读取加载到内存的图像元数据(Data Buffers),然后经过Decode,将元数据解码为直接表示像素点信息的Image Buffer,最后交由UIImageView渲染到屏幕上.流程如下图所示的:

sample6

解码(Decoding)

而解码过程(将DataBuffer解码到Image Buffers)是一个高密度计算的操作,需要CPU的高强度的计算来满足.而解码过程中,处理数据量的大小只与Data Buffer保存到原数据大小相关,跟渲染的UIImageView大小是没关系的.

所以,这其中就有一个地方其实是有优化的余地的~比方说如果我们的应用需要加载多张清晰度较高的照片,如果没有任何优化处理,直接读取图片,然后直接渲染,那么在Decode步骤的时候,就会发现操作”吃掉”了内存和CPU的大部资源,甚至会直接造成卡死.但是,其实我们用来展示的图片(ImageView)的大小,完全用不到DataBuffer表示的这么大原始数据量.

所以以上步骤我们就有优化的余地~怎么优化呢,通过如下图所示的缩略图的方式:

sample7

代码实现如下图所示(Objective-C),当然了事物必有两面性,编程也是一样的,不是牺牲时间换空间,就是牺牲空间换时间.虽然通过如下方式,我们成功地降低了内存的使用,但是解码这件事情,同样还是会消耗掉大量的CUP计算资源,而CUP不仅仅只需要做处理图片这件事,还有很多其他更重要的任务都需要CPU来处理,当我们通过这种方式大批量的处理图片的时候,依然很容易造成CPU任务处理的阻塞,进而表现为界面卡顿等.所以还需要有进一步的处理来解决这种情况的发生,而解决的方法就是:Prefetching + Background decoding.

nWvG3.jpg

Prefetch是在iOS10之后新进到TableView和CollectionView两种展示视图的新概念.其实就是预加载的意思,并且官方为这两种最常见的滚动展示控件提供了集成好的预加载接口,只需要传入数据就行了.我们这里也可以参照这个概念,对图片解码这个处理进行预加载,来缓解一部分CPU实时计算的压力.

Background decoding就更容易理解了,说白了就是后台处理,利用子线程来处理复杂的解码工作,然后讲结果在主线程进行使用.需要注意的是,在大批量处理图片的时候,最好是处理成串行队列,有限制机制来控制子线程的数量,不要每处理一张图片就开一个线程,不然等你开出来十几几十的线程的时候,CPU光用来处理切换线程的操作就已经疲于奔命了,更别提干正事了~

图片来源(Image Sources)

在App里面加载的图片基本上就是一下四种来源:

  1. Image Assets管理的图片
  2. Bundle、Framework等资源包里的图片
  3. app本地化到沙盒的图片
  4. 网络下载图片

而苹果WWDC的Session里边建议我们使用Image Assets,因为苹果在里边做了很多优化作业,

自定义绘制(Custom Drawing)

只要记住一个准则就可以了,除非万不得已,不然不要重载drawRect函数,原因就是重载drawRect函数会导致系统给UIView创建一个backing store,像素的数量是UIView大小乘以contentsScale的值,因此会消耗掉不少内存,而UIView并不是一定需要创建一个backing store的,比如设置背景色就不需要,如果需要设置复杂的背景色,直接使用UIImageView,因为UIImageView还做了一些优化处理.

离屏绘制(Drawing Off-Screen)

如果我们想要自己创建Image Buffer,我们通常会选择使用UIGraphicsBeginImageContext(),而苹果方面建议的是使用UIGraphicsImageRenderer,因为它的性能更好,而且还支持广色域.

相关资料

  1. WWDC2018 Image and Graphics Best Practices
  2. WWDC心得: Advanced Graphics and Animations for iOS Apps
  3. WWDC2018图像最佳实践
  4. iOS的5中图片缩略技术和性能探讨
文章目录
  1. 1. 图片渲染的编码原理
    1. 1.1. 图片渲染管线
    2. 1.2. 缓冲区
    3. 1.3. 解码(Decoding)
    4. 1.4. 图片来源(Image Sources)
    5. 1.5. 自定义绘制(Custom Drawing)
    6. 1.6. 离屏绘制(Drawing Off-Screen)
  2. 2. 相关资料