前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >iOS的GIF动画效果实现

iOS的GIF动画效果实现

作者头像
博文视点Broadview
发布于 2020-06-11 09:45:21
发布于 2020-06-11 09:45:21
1.4K00
代码可运行
举报
运行总次数:0
代码可运行

小编说:GIF图像格式是常见的一种动态图片格式,无论是在Web端还是在移动端都经常遇到,但是考虑目前iOS还无法原生展现GIF图片,而对于GIF的原生支持暂时也没有像JPG、PNG等图像格式支持得这么全面,因此本文从图片的合成与分解角度来为大家讲解GIF的知识,结合ImageIO框架可以更方便地实现GIF图片的合成与分解。 本文选自《iOS动画——核心技术与案例实战》

  • GIF在iOS中的使用场景

GIF在iOS中的使用场景有以下三个方面。

(1)GIF图片分解为单帧图片。

(2)一系列单帧图片合成GIF图片。

(3)iOS系统上展示GIF动画效果。

在GIF的合成和分解方面将会接触到iOS图像处理核心框架ImageIO,作为iOS系统中图像处理的核心框架,它为我们提供了各种丰富的API,本文将要实现的GIF分解与合成功能,通过ImageIO就可以很方便地实现。GIF动画展示效果将结合UIImageView和定时器,利用逐帧展示的方式为大家呈现GIF动画效果。

  • GIF分解单帧图片

1 GIF图片分解过程

GIF分解为单帧图片的过程如下。

整个过程划分为5个模块、4个过程,分别如下。

(1)本地读取GIF图片,将其转换为NSdata数据类型。

(2)将NSData作为ImageIO模块的输入。

(3)获取ImageIO的输出数据:UIImage。

(4)将获取到的UIImage数据存储为JPG或者PNG格式保存到本地。

在整个GIF图片分解的过程中,ImageIO是处理过程的核心部分。它负责对GIF文件格式进行解析,并将解析之后的数据转换为一帧帧图片输出。幸运的是我们并不是“轮子”的创造者,而是只要使用轮子即可。所以在本书中我们不去研究GIF分解合成算法的具体实现方式,而是将注意力聚焦在如何使用ImageIO框架实现需要的功能上。

2 GIF图片分解代码实现

在正式分析代码之前,先来看看整个工程的文件结构,如图。

源文件使用的是plane.gif文件。ViewController.swift文件中的viewDidLoad()方法中包含了GIF图片分解为单帧图片并保存到本地的所有代码。下面就结合“GIF分解为单帧图片的过程”来实现这一功能。

功能模块一:读取GIF文件并将之转换为NSdata类型。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
1 let gifPath:NSString = Bundle.main.path(forResource: "plane", ofType: "gif")! as NSString
2 let gifData:Data = try! Data(contentsOf: URL(fileURLWithPath: gifPath as String))

代码第1行通过path方法获取文件名为plane、文件格式为gif的文件地址。第2行获取文件信息并加载到gifData(NSData类型)变量中。至此已经完成整个处理流程的第一个环节。

功能模块二:利用ImageIO框架,遍历所有GIF子帧。需要注意的是使用ImageIO必须把读取到的NSdata数据转换为ImageIO可以处理的数据类型,这里使用CGImageSourceRef实现。其相应功能模块的处理流程如下所示。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
1 let gifDataSource:CGImageSource =CGImageSourceCreateWithData(gifData as CFData, nil)!
2 let gifImageCount:Int =CGImageSourceGetCount(gifDataSource)
3 for i in 0...gifImageCount-1{
   let imageref:CGImage? =CGImageSourceCreateImageAtIndex(gifDataSource, i, nil)
   let image:UIImage =UIImage(cgImage:scale:UIScreen.main.scale,orientation:UIImageOrientation.up )
 }

下面是GIF数据处理流程中ImageIO部分功能描述。代码第1行实现将GIF原始数据类型NSdata转换为ImageIO可以直接处理的数据类型CGImageSourceRef。第2行获取当前GIF图片的分帧个数。我们知道GIF图片都是由一帧帧图片组成的,那么这一行就是为了获取构成GIF图片的张数。第3行对CGImageSource数据按照图片的序号进行遍历,将遍历出的结果使用UIImage系统方法将之转换为UIImage。

这里重点为大家介绍两种方法。

CGImageSourceCreateImageAtIndex方法的作用是返回GIF中其中某一帧图像的CGImage类型数据。该方法有三个参数,参数1为GIF原始数据,参数2 为GIF子帧中的序号(该序号从0开始),参数3为GIF数据提取的一些选择参数,因为这里不是很常用,所以设置为nil。

public func CGImageSourceCreateImageAtIndex(_ isrc: CGImageSource, _ index: Int, _ options: CFDictionary?) -> CGImage?

以下为UIImage类的方法,这个方法用于实例化UIImage实例对象。该方法有三个参数,参数1为需要构建UIImage的内容,注意这里的内容是CGImage类型,参数2为手机物理像素与手机和手机显示分辨率的换算系数,参数3表明构建的UIImage的图像方向。通过这个方法就可以在某种手机分辨率下构建指定方向的图像,当然图像的类型是UIImage类型。

public init(CGImage cgImage: CGImage, scale: CGFloat, orientation: UIImageOrientation)

通过上述两步已经获取了UIImage,然而UIImage并不是通常我们看到的图像格式,此图像格式最大的特点是无法存储为本地可以查看的图片格式,因此如果需要将图像保存在本地,就需要在这之前将已经得到的UIImage数据类型转换为PNG或者JPG类型的图像数据,然后才能把图像存储到本地。

下面是完整的GIF图像分解保存代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
override func viewDidLoad() {
1   super.viewDidLoad()
2   let gifPath:NSString = Bundle.main.path(forResource: "plane", ofType: "gif")! as NSString
3   let gifData:Data = try! Data(contentsOf:URL(fileURLWithPath: gifPath as String))
4   let gifDataSource:CGImageSource =CGImageSourceCreateWithData(gifData as CFData, nil)!
5   let gifImageCount:Int = CGImageSourceGetCount(gifDataSource)
6   for i in 0...gifImageCount-1{
7       let imageref:CGImage? = CGImageSourceCreateImageAtIndex(gifDataSource, i, nil)
8       let image:UIImage = UIImage(cgImage: imageref!,scale:UIScreen.main.scale,orientation:UIImageOrientation.up )
9       let imageData:Data = UIImagePNGRepresentation(image)!
10      var docs=NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
11      let documentsDirectory = docs[0] as String
12      let imagePath = documentsDirectory+"/\(i)"+".png"
13      try? imageData .write(to: URL(fileURLWithPath:imagePath), options: [.atomic])
14      print("\(imagePath)")
   }
}

代码第1行使用UIImagePNGRepresentation方法将UIImage数据类型存储为PNG格式的data数据类型,第2行代码和第3行代码获取应用的Document目录,第4行调用write方法将图片写入到本地文件中。如果大家想查看最终写入的效果,可以在最后一行添加print信息,将文件写入路径打印出来,观察图像写入是否成功。

3 GIF图片分解最终实现效果

通过上述代码中的最后一行print("\(imagePath)")可以获取图片最终保存的路径。进入该路径下可以看到下图所示的图片最终分解结果。

根据上图,在Mac系统下,利用系统图片的查看工具来查看GIF图片的分帧结果,对比图中内容,可以看出GIF图片分解的结果是正确的。

  • 序列图像合成GIF图像

1 GIF图片合成思路

多帧图像合成GIF的过程和GIF分解多帧图像的过程互逆,GIF图片分解过程倒过来推,就是GIF图像合成的过程。这里将上面分解的67张序列单帧图像作为需要处理的输入源进行讲述。

从功能上来说,GIF图片的合成分为以下三个主要部分。

(1)加载待处理的67张原始数据源。

(2)在Document目录下构建GIF文件。

(3)设置GIF文件属性,利用ImageIO编码GIF文件。

2 GIF图片合成代码实现

如下代码是根据GIF构建的三个主要步骤进行编写的。第一部分代码的功能是将67张PNG图片读取到NSMutableArray数组中。代码第1行初始化可变数组,第2行遍历67张本地图片,第3行按照图片的命名规律,构建67张图片名称,第4行加载本地图片。最后一行将读取的图片依次加载到images可变数组中。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// Part1:读取67张png图片
1   let images:NSMutableArray = NSMutableArray()
2   for i in 0...66{// 遍历本地67张图片
3       let imagePath = "\(i).png" // 构建图片名称
4       let image:UIImage = UIImage(named: imagePath)!//
5       images.addObject(image)// 将图片添加到数组中}

代码第二部分的功能是构建在Document目录下的GIF文件路径。具体实现如下所示。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// Part2:在Document目录创建gif文件
1    var docs=NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
2    let documentsDirectory = docs[0] as String
3    let gifPath = documentsDirectory+"/plane.gif"
4    print("\(gifPath)")
5    let url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, gifPath as CFString!,CFURLPathStyle.cfurlposixPathStyle, false)
6    let destion = CGImageDestinationCreateWithURL(url!,kUTTypeGIF, images.count, nil)

代码1一行和第2行获取Document路径地址,第3行代码通过字符串拼接时组成完整的Document路径下plane.gif文件路径。为了方便查看GIF文件所在路径,第4行代码将GIF文件路径打印出来。第5行代码将plane.gif文件路径由string类型转换为URL类型。最后一行代码是ImageIO中构建GIF图片非常重要的方法,我们重点来分析该方法的作用和功能。

public func CGImageDestinationCreateWithURL(_ url: CFURL, _ type: CFString, _ count: Int, _ options: CFDictionary?) -> CGImageDestination?

CGImageDestinationCreateWithURL方法的作用是创建一个图片的目标对象,为了便于大家理解,这里把图片目标对象比喻为一个集合体。

CGImageDestination结构

集合体中描述了构成当前图片目标对象的一系列参数,如图片的URL地址、图片类型、图片帧数、配置参数等。本代码中将plane.gif的本地文件路径作为参数1传递给这个图片目标对象,参数2描述了图片的类型为GIF图片,参数3表明当前GIF图片构成的帧数,参数4暂时给它一个空值。

到目前为止,待处理图片源已经加载到代码中,GIF图片Destination也已经完成构建,下面就需要使用ImageIO框架把多帧PNG图片编码到GIF图片中,其处理流程如下。

具体实现代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// Part3:设置gif图片属性,利用67张png图片构建gif
1  let cgimagePropertiesDic = [kCGImagePropertyGIFDelayTime asString:0.1]//设置每帧之间播放时间
2  let cgimagePropertiesDestDic = [kCGImagePropertyGIFDictionary as String:cgimagePropertiesDic];
3  for cgimage in images{
4      CGImageDestinationAddImage(destion!, (cgimage as AnyObject).cgImage!!,cgimagePropertiesDestDic as CFDictionary?);
}// 依次为gif图像对象添加每一帧元素
5  let gifPropertiesDic:NSMutableDictionary = NSMutableDictionary()
6  gifPropertiesDic.setValue(kCGImagePropertyColorModelRGB, forKey: kCGImagePropertyColorModel as String)
7  gifPropertiesDic.setValue(16, forKey: kCGImagePropertyDepth as String)// 设置图像的颜色深度
8  gifPropertiesDic.setValue(1, forKey: kCGImagePropertyGIFLoopCount as String)// 设置Gif执行次数
9  let gifDictionaryDestDic = [kCGImagePropertyGIFDictionary as String:gifPropertiesDic]
10 CGImageDestinationSetProperties(destion!,gifDictionaryDestDic as CFDictionary?);//为gif图像设置属性
11 CGImageDestinationFinalize(destion!);

代码第1行设置GIF图片属性,设置当前GIF中每帧图片展示时间间隔为0.1s。代码第2行构建一个GIF图片属性字典,字典使用GIF每帧之间的时间间隔初始化。代码第4行使用遍历的方法将已经准备好的图片快速追加到GIF图片的Destination中。代码第5行初始化一个可变字典对象,该字典对象主要用于设置GIF图片中每帧图片属性。第6行设置图片彩色空间格式为RGB(Red Green Blue三基色)类型。第7行设置图片颜色深度。一般来说黑白图像也称为二值图像,颜色深度为1,表示2的一次方,即两种颜色:黑和白。灰度图像一般颜色深度为8,表示2的8次方,共计256种颜色,即从黑色到白色的渐变过程有256种。对于彩色图片来说一般有16位深度和32位深度之说,这里设置为16位深度彩色图片。代码第8行设置GIF图片执行的次数,这里设置为执行一次。代码第9行和第10行负责将以上图片设置的各种属性添加到GIF的Destination目标中。最后一行完成GIF的Destination目标文件构建。

可以打印出当前GIF图片的路径,在该路径下可以看到最终生成的GIF图片。

  • Gif图像展示

iOS原生并不支持直接显示GIF图片,由前面的分析可知,GIF图片由一帧帧的单帧图片构成,所以只要实现GIF图片的分解,接下来就是多组图片显示的问题了。为大家介绍另外一种图片展现形式,即基于UIImageView展现GIF多帧图片。

经过对GIF图片展示思路的分析可以知道,在iOS下展现GIF分为两步:第一步分解GIF图片为单帧图片,第二步在iOS下展现多帧图片。UIImageView是一个用来展现图片的UI组件,不过它还有一些动画属性可以用来进行逐帧动画展现。

考虑到第一步GIF图片已经分解,所以这里把分解之后的67张图片先加载进来。

UIImageView多帧图像展示具体实现代码如下。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
1  var images:[UIImage] = []
2  for i in 0...66{// 遍历本地67张图片
3       let imagePath = "\(i).png" // 构建图片名称
4       let image:UIImage = UIImage(named: imagePath)!
5       images.append(image)// 将图片添加到数组中
  }
6  let imageView = UIImageView()
7  imageView.frame = self.view.bounds
8  imageView.contentMode = UIViewContentMode.Center
9  self.view.addSubview(imageView)
10 imageView.animationImages = images
11 imageView.animationDuration = 5
12 imageView.animationRepeatCount = 1
13 imageView.startAnimating()

代码第1行初始化一个子元素为UIImage类型的数组对象。第2行到第5行通过for循环将67张图片依次加载到当前数组中。第6行实例化一个UIImageView实例对象。第7行和第8行设置UIImageView实例对象的frame位置属性以及图片的拉伸方式,这里设置为居中显示。第9行将UIImageView添加到self.view图层上。第10行将初始化加载的67张图片添加到UIImageView实例的animationImages上,相当于设置UIImageView的内容。第11行设置UIImageView图片动画播放周期。第12行设置动画重复次数。最后一行启动UIImageView多帧图片展示动画。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2017-02-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 博文视点Broadview 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
用python实现的百度音乐下载器-python-pyqt-改进版
摘要总结:本文介绍了一个用Python实现的百度音乐下载器,包括其爬虫程序、解析歌曲信息、下载歌曲以及UI界面实现等。该下载器支持快速扫描并下载热门歌曲,同时也可以登录百度账号进行歌曲的下载。通过使用多线程、BeautifulSoup和HtmlParser等技术,实现了快速下载歌曲的功能。
古时的风筝
2018/01/08
1.1K0
用python实现的百度音乐下载器-python-pyqt-改进版
《一头扎进》系列之Python+Selenium自动化测试框架实战篇6 - 价值好几K的框架,呦!这个框架还真牛叉哦!!!
  本文开始介绍如何通过unittest来管理和执行测试用例,这一篇主要是介绍unittest下addTest()方法来加载测试用例到测试套件中去、用addTest()方法来加载我们测试用例到suite中去和利用discover()方法去加载一个路径下所有的测试用例。
北京-宏哥
2019/12/26
9770
《一头扎进》系列之Python+Selenium自动化测试框架实战篇6 - 价值好几K的框架,呦!这个框架还真牛叉哦!!!
python 获取当前目录 上一级目录 上上一级目录
一 目录层级关系如下图 当前py文件为当前目录下的python.py 二 获取当前目录 上级目录 上上级目录 import os print('***获取当前目录***') print("当前目
用户8346838
2021/03/11
15.8K0
Python自动化运维开发必备技能,学之会受用无穷~
最近在做数据流转的自动化项目,经常要用到一个模块 -- os模块,而这个模块在自动化运维开发过程中,经常要用到,索性今天就对这个模块的相关内容做一个学习和分享。该模块也是自动化运维开发的必备技能,也是DBA的必备技能哦!学之受用无穷;
SEian.G
2021/04/15
6140
Python 基础模块学习
os 负责程序与操作系统交互,提供访问操作系统底层的接口, (创建目录,删除,获取属性、获取路径,获取文件名,判断文件和目录是否存在)
Linux运维技术之路
2022/06/07
4800
Python 基础模块学习
Python入门之获取当前所在目录的方法详解
#本文给大家讲解的是使用python获取当前所在目录的方法以及相关示例,非常的清晰简单,有需要的小伙伴可以参考下 sys.path 模块搜索路径的字符串列表。由环境变量PYTHONPATH初始化得到。 sys.path[0]是调用Python解释器的当前脚本所在的目录。 sys.argv 一个传给Python脚本的指令参数列表。 sys.argv[0]是脚本的名字(由系统决定是否是全名) 假设显示调用python指令,如 python demo.py ,会得到绝对路径; 若直接执行脚本,如 ./demo.p
Jetpropelledsnake21
2018/05/03
1.3K0
Python基础之os和数据结构
今天总结了下Python的基础,发现还是有很多基础需要巩固,直接把学习的内容放上来。 >>> import os 得到当前的所在的路径 >>> os.getcwd() '/root/test' 列出当前路径所在的文件夹下的文件 >>> os.listdir(os.getcwd()) ['a.py', 'redis_test.sql', 'cmdb_server.txt', 'a.sql', 'test.py', 'redis_test.txt', 'paramiko.pyc', 'cmdb_server.t
jeanron100
2018/03/22
8450
Python3目录操作
输出: 'D:\python\jupyter' 'D:\python' ['.ipynb_checkpoints', 'bak', 'jupyter', 'project', 'test.txt', 'testdir', 'Untitled.ipynb', ]
py3study
2020/01/03
7860
python学习笔记10.1 python中的路径
python中‘.’和os.getcwd()是等价的,是运行python文件的工作目录,而不是被运行的文件所在目录,它是随着工作目录变化的。
锦小年
2021/12/08
7410
python学习笔记10.1 python中的路径
python3获取文件目录和文件
python3获取文件目录和文件 import os,sys if __name__=="__main__": print("__file__ = %s" % __file__) #获取文件相对路径 print("sys.argv[0]) = %s" % sys.argv[0]) #获取文件的全路径加文件名 print("sys.path[0] = %s " % sys.path[0]) #获取文件的全路径 print("os.getcwd() = %s" % os.getcwd()) #
py3study
2020/01/03
8510
小说python的路径操作
在日常编码中,常常会有这些操作 在当前目录或用户目录下新建一个配置文件, 获取一个文件的路径或上级目录 这些都涉及路径操作 相信大家都使用过`os.path`来处理过,这个痛苦我想"谁用谁知道吧" 今天的主角`pathlib`就是来解决痛苦的... pathlib简介 pathlib是跨平台的、面向对象的路径操作库, pathlib就是对os.path进行了封装,提供了一个便捷的,面向对象的操作方式, 相对os.path作为string对象需要进行众多繁琐的操作, 它才真正是for humans pyth
用户2196567
2018/07/02
8730
看完这篇,再也不用头疼文件「路径」的问题啦
在实际应用过程中,我们经常会和各种文件打交道,「文件」是计算机中非常重要的东西,之前写过两篇关于 Python 操作文件的文章,不熟悉的可以先看看:
编程文青李狗蛋
2019/11/07
3900
python - 模块
参考:https://www.cnblogs.com/nulige/p/6166205.html
py3study
2020/01/14
7480
Python - 超好用的第三方库pathlib,快速获取项目中各种路径
之前曾介绍过Python的os库详细使用方式,具体可看看这篇博文:https://www.cnblogs.com/poloyy/p/12341231.html
小菠萝测试笔记
2020/06/09
1.2K0
Python - 超好用的第三方库pathlib,快速获取项目中各种路径
Python常用库 - os库
os.write(fd, str) 用于写入bytes字符串到文件描述符 fd 中. 返回实际写入的字符串长度
小菠萝测试笔记
2020/06/09
1.4K0
Python3 IO编程
IO 在计算机中指 Input/Output,也就是输入和输出。由于程序和运行时数据是在内存中驻留,由 CPU 这个超快的计算核心来执行,涉及到数据交换的地方,通常是磁盘、网络等,就需要 IO 接口。 IO 编程简单理解指的是你的程序涉及到 cpu、内存和磁盘、网络的数据交互。因为 cpu、内存和磁盘、网络的速度有差异,所以在 IO 编程中,就存在速度严重不匹配的问题,比如网络爬虫的实例,网页解析可能只要 0.001秒,但是下载网页可能要 0.1s,为解决速度不匹配的问题,可以使用异步 IO,使用异步 IO 来编写程序性能会远远高于同步 IO,但是异步 IO的缺点是编程模型复杂。 操作 IO 的能力都是由操作系统提供的,每一种编程语言都会把操作系统提供的低级 C 接口封装起来方便使用, Python 也不例外。
嵌入式视觉
2022/09/05
5300
Python3 IO编程
Python 文件及文件夹操作记录
# coding=utf-8 import os, sys # 文件目录的斜杠,使平台无关 print os.sep # 回到上级目录 print os.path.pardir print os.path.join(os.path.dirname("__file__"), os.path.pardir) # ---------------------------------------------------------------------------------------------- #
白墨石
2021/01/13
4380
python os模块
删除单级空目录,若目录不为空则无法删除,报错;相当于shell中rmdir dirname
py3study
2018/08/02
8100
python os模块详解
os模块提供对操作系统进行调用的接口 1、获取当前的路径 >>> import os >>> os.getcwd() //相当于Linux命令pwd '/root' 2、切换目录 >>> os.chdir("/usr/local") //相当于Linux命令cd >>> os.getcwd() '/usr/local' 3、递归的创建目录 >>> os.makedirs("/a/b/c") //makedirs能达到递归创建目录的功能 >>> os.chdir("/a
IT架构圈
2018/06/01
8530
Python-os-01-获取当前文件所在文件夹路径
系统:Windows 7 语言版本:Anaconda3-4.3.0.1-Windows-x86_64 编辑器:pycharm-community-2016.3.2
zishendianxia
2019/10/23
1.5K0
相关推荐
用python实现的百度音乐下载器-python-pyqt-改进版
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档