图像和图形的最佳实践(WWDC 2018 session 219)

2024-04-26 02:48

本文主要是介绍图像和图形的最佳实践(WWDC 2018 session 219),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

该篇博客记录观看WWDC2018中Session219《Image And Graphics Best Practices》的内容及一些理解。

该Session主要讲述了关于有效使用图形内容的一些技术和策略。主要分三个方面:

  1. 从UIImage和UIImageView入手,讲述UIKit对于图形内容的处理。
  2. 讨论使用UIKit高效的处理自定义绘图。
  3. 简单讲述在应用中使用先进的CPU和GPU技术。

在整个讨论中,主要讨论图形处理对于内存CPU的影响,这两个因素可以影响到系统的反应速度以及电池的使用寿命。

UIImage和UIImageView

UIImage在UIKit中表示一个图形的内容,而UIImageView在UIKit中用来呈现一个视图。对应到MVC模式中,UIImage是一个加载图形内容的model,而UIImageView是显示渲染图形的视图。这两者之间的关系是一种连续的一次性的简单单向联系。如下图所示:
UIImage与UIImageView关系

但是在这之外还有一个隐藏的、影响程序性能的过程,叫做解码(Decode),如下图:
Decode

Decode

在了解Decode的过程中,我们首先要了解一个概念:缓冲区(Buffer)。

  1. 缓冲区是在内存中连续的区域。
  2. 通常被视为元素序列。
    缓冲区(Buffer)
1.图像缓冲区(Image Buffer)

图像缓冲区中每个元素表示的是图像中一个像素的颜色信息。所以,该Buffer在内存中的大小与图像大小成正比。

2.帧缓冲区(Frame Buffer)

帧缓冲区是保存应用实际呈现输出的缓冲区。

在应用更新视图层次结构时,UIKit会把应用程序的Window以及子视图渲染到帧缓冲区中。这个更新频率在iPhone上是60FPS,在iPad上是120FPS。
帧缓冲区(Frame Buffer)

3.数据缓冲区(Data Buffer)

数据缓冲区为保存一系列bytes数据的缓冲区。

在图像例子中,数据缓冲区就是保存从网络下载或者保存在磁盘中的图像的数据,这些数据并不直接描述每一个像素的信息。
数据缓冲区(Data Buffer)

加载过程

现在,我们了解了Buffer以及几种与图像相关的Buffer,接下来可以分析一下图片加载过程了。

  1. 加载准备:我们准备一个图像元数据,一个UIImage,以及一个加载在视图上的UIImageView,如下:
    加载准备
  2. 获取像素信息:为了将图像中每个像素信息填充到Frame Buffer中,我们需要得到图像像素信息。UIImage会为我们处理这一点:UIImage会创建一个与图片大小一致的Image Buffer用来存储解码(Decode)之后的图像像素信息。
    获取像素信息
  3. 显示在屏幕上:UIImageView读取UIImage创建的Image Buffer数据,交由UIKit显示。UIKit会对像素数据缩放到显示大小进行显示。
    显示在屏幕上

在加载过程中,UIKit会重复多次的要求UIImageView去进行渲染,这就造成了UIImage会多次的解码Data Buffer。

为了解决这个问题,UIImage会只进行一次Data Buffer的解码,在解码之后,会挂起解码后的Image Buffer。

所以,在这种情况下,应用对于每一个UIImage解码后,都会在内存中挂起一个Image Buffer。

由于Image Buffer与图像大小成正比,所以在加载大图片时,应用程序会在内存中挂起很多大的Image Buffer,这可能会造成操作系统介入,并最终杀死应用。

下采样(downsampling)

针对于我们上述提到的问题,我们可以采用叫做下采用(downsampling)的技术。

我们注意到,我们最终显示在屏幕上的视图往往比实际图片的尺寸要小,而通常情况下,Core Animation Framework会负责缩小图像。
缩放显示

现在要做的,本质上是把缩放图像操作捕获为一个叫缩略图的对象。由于减小了Image Buffer的大小,进而减小了在加载图片中的使用的内存总大小。

同时,我们在解码后,将图片像素信息交给UIImageView去渲染至屏幕后,可以丢弃掉对应的Data Buffer,进一步减少内存的使用。

这个过程如下图:
下采样过程

接下来我们放在代码中进行一下分析,整体代码如下:
下采样整体代码
接下来我们分析几个重要的点:

  1. 创建CGImageSourceRef
    创建CGImageSourceRef
    在创建CGImageSourceRef的时候,CGImageSourceCreate方法可以接受一个选项设置,我们可以提供kCGImageSourceShouldCachefalse。这个选项设置告诉Core Graphics Frameword我们只是需要一个CGImageSourceRef来存储对应路径的文件的信息,而不需要立刻去对文件中的图像进行解码。

  2. 计算需要渲染至屏幕的真实尺寸
    计算需要渲染至屏幕的真实尺寸

  3. 获取缩略图
    获取缩略图
    在获取缩略图的方法CGImageSourceCreateThumbnailAtIndex中,我们可以指定选项设置,其中需要我们关注的是kCGImageSourceShouldCacheImmediately(iOS7.0及以上版本),该设定告诉Core Graphics Frameword,在进行创建缩略图的时候,就应该立刻创建一个解码后的Image Buffer。

通过以上的改进方式,可以大幅度的缩减内存的使用。以下为一组测试数据:
测试环境:Xcode9.4,iPhone 6s Plus, 5184*3456的5.1MB大小的JPG格式图片。

  • 不使用改进方法,消耗内存为:49.3M
  • 使用改进方法,消耗内存为:9.4M

针对滚动视图的优化

如果我们需要一个滚动视图来展示一大组质量很大的图片时,我们也会遇到内存过高的问题,接下来我们以UICollectionView加载图片来做分析和优化。

使用下采样(downsampling)的方式优化

首先我们采用我们上一步优化图片的方式来为每一个cell中的图片进行优化,代码可能如下:
下采样优化滚动视图

这样看起来很有用,因为我们为每一个cell上的图片显示进行了优化,进而使得整体内存使用有一个明显的降低。但是,这样会引起另一个问题:如果此时滚动视图,CPU可能会很快的将所需展示的内容渲染到帧缓冲区,但是如果滚动过快,CPU还需参与Core Graphics对于新图像的解帧,这个操作是非常耗时的,这样就可能会造成在硬件读取帧缓冲区数据显示至屏幕时,帧缓冲区数据并没有准备好,进而导致用户视角中的卡顿。另外对于电池来说,在CPU不稳定时,可能会影响到电池使用寿命。

进一步优化

有两种技术可以帮助我们解决这个问题。

  1. 预取(prefetching):在某一时刻,我们不需要某个cell,但是在不久的将来会需要这个cell,所以可以把某些工作提前至这个时刻来进行。

  2. 后台执行(performing work in the background)

针对于UICollectionView,我们可以做以下处理:
预取和后台执行
我们为预取的cell进行在后台即全局异步队列(global
asynchronous queues)的图像解码,这是我们提到的两种优化技术。

但是,在使用全局异步队列的时候,可能会出现线程爆炸的问题:如果我们一次性处理多张图像,但是设备只有2个CPU时,此时GCD会创建新的线程来处理解码工作。创建新线程以及在不同线程之间切换是十分消耗时间和资源的。

解决线程爆炸问题

我们可以将解码操作异步的分派到串行队列中,如下图:
解决线程爆炸问题
这样做可能使得某些图像的解码过程延后,但是更多的是减少了在线程切换过程中浪费的时间和资源。

至此,我们完成了对于滚动视图加载多张图片的优化。

图片资源的优化

接下来分析一下我们对于图片资源的处理,在目前的程序中,可以展示的图像资源来源可能有一下几种:

  1. 存储在Image Assets中
  2. 存储在Application或者Bundle的包中
  3. 存储在沙盒的Document或者Cache文件中
  4. 从网络下载的数据中

对于程序中自带的图像资源,苹果官方推荐我们使用Image Assets来存储,以下是使用该方式的几个优点:

  1. Image Assets针对基于名称和特性的查找进行了优化,它比在磁盘上搜索文件要快。
  2. 在管理缓冲区方面也有优化。
  3. 允许对设备安装包进行优化,在下载App的时候会只下载能最高效果显示在对应设备的图像资源,从而减小安装包大小。
  4. 对Vector Artwork的支持。
Vector Artwork

Vector Artwork是iOS 11引入的新特性。我们可以在Image Assets中勾选Preserve Vector Data来启用它。如果启用了Vector Artwork的话,当我们将图像显示到一个比它原始尺寸大或者小的视图中时,这个图像不会变的模糊。因为显示的图像是从矢量图形中重新光栅化而来的,从而使得图像有清晰的边缘。

启用Vector Artwork后,图像的处理方式和之前图片的处理方式类似,只是将解码阶段变为了光栅化阶段,光栅化阶段将矢量数据转换为位图数据,进而供帧缓冲区读取。

Vector Artwork读取流程

如果我们把应用中所有的图像资源进行了Preserve Vector Data处理的话,会造成一些CPU的使用。
所以Xcode对这些情况做了一些处理:如果选中了Preserve Vector Data选项,但是在视图上展示为原始尺寸大小时,Image Assets实际上已经完成了原始尺寸的光栅化,并把相关数据保存在Image Assets中了,所以这种情况可以直接对存储的数据进行解码,而不进行光栅化。

另外一个建议是:如果计划展示的尺寸为固定的几个尺寸,那么不要依赖Preserve Vector Data,而是准备好对应尺寸的图像资源,从而加快CPU对图像资源的处理。

自定义绘制内容

我们有时需要在程序中进行一些自定义的绘制,例如绘制一个如下样式的视图:
自定义控件

我们可以继承UIView,然后在draw方法中进行相应的绘制:
自定义控件实现

但是我们并不推荐这么使用,我们首先对比UIImageView和draw的实现原理来进行分析:我们都知道UIView是基于CALayer来显示内容的;而对于UIImageView来说,UIImageView会将UIImage解码后的数据交给CALayer来作为内容显示;而对于draw来说,CALayer会创建一个与图像大小成正比的backing store来存储图像数据,然后将图像数据拷贝至backing store中,然后将backing store中的内容绘制到帧缓冲区中。

由此可见,通过重写draw方法可能会造成多余的内存消耗以及数据的拷贝,接下来了解一下backing store

Backing Store

在我们重写draw方法时,会触发创建Backing Store,此时的Backing Store的大小与视图的像素大小成正比。

同时,在iOS12中,Backing Store将会使用动态增长的方式来减少内存的使用。在以前的iOS版本中,我们可以设置CALayer的contentsFromat属性来指定Core Animation Framework在绘制时用到的颜色长度,这个设置会关闭iOS12中关于Backing Store的设置。

建议实现方式

回归主题,对于我们需要自定义绘制的视图,我们应该减少Backing Store的使用。通常的做法是将一个大视图构建为多个小视图来实现;同时,系统提供的经过优化的视图属性也不会造成多余内存的使用(例如UIView的backgroundColor属性,就不会创建Backing Store。此时需要注意,使用pattern color时是一个例外,可以使用UIImageView作为UIView的子视图来替代这一效果)。

将大视图构建为多个小视图

在Live视图中,我们可以构建为以下层级:
Live视图层级

1.圆角处理

在我们要进行圆角处理时,使用CALayer的cornerRadius属性,该属性不会引起多余的内存使用。而对于使用maskView和maskLayer来说,会使用到额外的内存空间。

同时对于圆角之外的透明区域为复杂的不规则形状且cornerRadius满足不了需求时,可以使用UIImageView配合Resizeable的UIImage来进行处理。

2.Icon的实现

Icon可以使用UIImageView来显示。

为了达到UIImage的复用,UIImageView支持对单色图像着色,同时可以直接渲染到帧缓冲区中。

这个设置需要对UIImage进行renderingMode的设置或者在Image Assets中进renderingMode设置。

最后,为UIImageView设置tintColor来完成最终颜色渲染。

3.UILabel的优化
  • UILabel对于显示单色字符串做了优化处理,可以节省75%大小的Backing Store的使用。

  • UILabel对于多彩字符串以及emoji进行了动态增长Backing Store的优化。

最后,如果想离屏绘制,就是用UIGraphicsImageRenderer,而不是使用旧版本的UIGraphicsBeginImageContext。因为UIGraphicsImageRenderer支持宽颜色内容展示;同时也支持根据绘制的动作来动态的增加Image Buffer的大小。在iOS12中,UIGraphicsImageRendererFormatprefersExtendedRange属性可以告知UIKit是否需要绘制宽颜色内容。同时,UIImage中提供了imageRendererFormat属性来配合UIGraphicsImageRenderFormat来使用。

简单讲述在应用中使用先进的CPU和GPU技术。

Advanced Image Effects

  1. 实时处理图像时考虑Core Image。
  2. 尽量使用GPU来处理图像,进而解放CPU。
  3. 使用CIImage创建UIImage,并交由UIImageView去展示,这个过程会默认由GPU去处理,减少对CPU的使用。

Advanced Image Processing

  1. 使用CVPixelBuffer将数据移动到Metal, Vision和Accelerate等框架。
  2. 使用最好的初始值设定项(不要重复执行已经完成的工作)。
  3. 防止在GPU和CPU直接来回切换。
  4. 确保交给Accelerate的buffers是正确的格式。

总结

  1. 异步的进行预处理。
  2. 使用UIImageView和UILabel来减少Backing Store的使用。
  3. 不要禁止自定义绘制时系统做的新优化。
  4. 使用Image Assets来存储图像。
  5. 如果要展示相同图片的不同尺寸,不要过多依赖Preserve Vector Data。

这篇关于图像和图形的最佳实践(WWDC 2018 session 219)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/936515

相关文章

基于MySQL Binlog的Elasticsearch数据同步实践

一、为什么要做 随着马蜂窝的逐渐发展,我们的业务数据越来越多,单纯使用 MySQL 已经不能满足我们的数据查询需求,例如对于商品、订单等数据的多维度检索。 使用 Elasticsearch 存储业务数据可以很好的解决我们业务中的搜索需求。而数据进行异构存储后,随之而来的就是数据同步的问题。 二、现有方法及问题 对于数据同步,我们目前的解决方案是建立数据中间表。把需要检索的业务数据,统一放到一张M

基于人工智能的图像分类系统

目录 引言项目背景环境准备 硬件要求软件安装与配置系统设计 系统架构关键技术代码示例 数据预处理模型训练模型预测应用场景结论 1. 引言 图像分类是计算机视觉中的一个重要任务,目标是自动识别图像中的对象类别。通过卷积神经网络(CNN)等深度学习技术,我们可以构建高效的图像分类系统,广泛应用于自动驾驶、医疗影像诊断、监控分析等领域。本文将介绍如何构建一个基于人工智能的图像分类系统,包括环境

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

系统架构师考试学习笔记第三篇——架构设计高级知识(20)通信系统架构设计理论与实践

本章知识考点:         第20课时主要学习通信系统架构设计的理论和工作中的实践。根据新版考试大纲,本课时知识点会涉及案例分析题(25分),而在历年考试中,案例题对该部分内容的考查并不多,虽在综合知识选择题目中经常考查,但分值也不高。本课时内容侧重于对知识点的记忆和理解,按照以往的出题规律,通信系统架构设计基础知识点多来源于教材内的基础网络设备、网络架构和教材外最新时事热点技术。本课时知识

BUUCTF靶场[web][极客大挑战 2019]Http、[HCTF 2018]admin

目录   [web][极客大挑战 2019]Http 考点:Referer协议、UA协议、X-Forwarded-For协议 [web][HCTF 2018]admin 考点:弱密码字典爆破 四种方法:   [web][极客大挑战 2019]Http 考点:Referer协议、UA协议、X-Forwarded-For协议 访问环境 老规矩,我们先查看源代码

如何确定 Go 语言中 HTTP 连接池的最佳参数?

确定 Go 语言中 HTTP 连接池的最佳参数可以通过以下几种方式: 一、分析应用场景和需求 并发请求量: 确定应用程序在特定时间段内可能同时发起的 HTTP 请求数量。如果并发请求量很高,需要设置较大的连接池参数以满足需求。例如,对于一个高并发的 Web 服务,可能同时有数百个请求在处理,此时需要较大的连接池大小。可以通过压力测试工具模拟高并发场景,观察系统在不同并发请求下的性能表现,从而

Prometheus与Grafana在DevOps中的应用与最佳实践

Prometheus 与 Grafana 在 DevOps 中的应用与最佳实践 随着 DevOps 文化和实践的普及,监控和可视化工具已成为 DevOps 工具链中不可或缺的部分。Prometheus 和 Grafana 是其中最受欢迎的开源监控解决方案之一,它们的结合能够为系统和应用程序提供全面的监控、告警和可视化展示。本篇文章将详细探讨 Prometheus 和 Grafana 在 DevO

springboot整合swagger2之最佳实践

来源:https://blog.lqdev.cn/2018/07/21/springboot/chapter-ten/ Swagger是一款RESTful接口的文档在线自动生成、功能测试功能框架。 一个规范和完整的框架,用于生成、描述、调用和可视化RESTful风格的Web服务,加上swagger-ui,可以有很好的呈现。 SpringBoot集成 pom <!--swagge

Verybot之OpenCV应用一:安装与图像采集测试

在Verybot上安装OpenCV是很简单的,只需要执行:         sudo apt-get update         sudo apt-get install libopencv-dev         sudo apt-get install python-opencv         下面就对安装好的OpenCV进行一下测试,编写一个通过USB摄像头采

vue2实践:el-table实现由用户自己控制行数的动态表格

需求 项目中需要提供一个动态表单,如图: 当我点击添加时,便添加一行;点击右边的删除时,便删除这一行。 至少要有一行数据,但是没有上限。 思路 这种每一行的数据固定,但是不定行数的,很容易想到使用el-table来实现,它可以循环读取:data所绑定的数组,来生成行数据,不同的是: 1、table里面的每一个cell,需要放置一个input来支持用户编辑。 2、最后一列放置两个b