zoukankan      html  css  js  c++  java
  • iOS的GIF动画效果实现

    引言: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图片分解代码实现

      在正式分析代码之前,先来看看整个工程的文件结构,如图。 
    【图2】
      源文件使用的是plane.gif文件。ViewController.swift文件中的viewDidLoad()方法中包含了GIF图片分解为单帧图片并保存到本地的所有代码。下面就结合“GIF分解为单帧图片的过程”来实现这一功能。 
    功能模块一:读取GIF文件并将之转换为NSdata类型。

    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类型)变量中。至此已经完成整个处理流程的第一个环节。 
    【图3】
      功能模块二:利用ImageIO框架,遍历所有GIF子帧。需要注意的是使用ImageIO必须把读取到的NSdata数据转换为ImageIO可以处理的数据类型,这里使用CGImageSourceRef实现。其相应功能模块的处理流程如下所示。 
    【图4】

    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: imageref!,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图像分解保存代码:

    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)”)可以获取图片最终保存的路径。进入该路径下可以看到下图所示的图片最终分解结果。 
    【图5 图6】

      根据上下图,在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可变数组中。

     // 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文件路径。具体实现如下所示。

     // 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结构
                          CGImageDestination结构 
      集合体中描述了构成当前图片目标对象的一系列参数,如图片的URL地址、图片类型、图片帧数、配置参数等。本代码中将plane.gif的本地文件路径作为参数1传递给这个图片目标对象,参数2描述了图片的类型为GIF图片,参数3表明当前GIF图片构成的帧数,参数4暂时给它一个空值。 
      到目前为止,待处理图片源已经加载到代码中,GIF图片Destination也已经完成构建,下面就需要使用ImageIO框架把多帧PNG图片编码到GIF图片中,其处理流程如下。 
    【图8】
      具体实现代码如下:

     // Part3:设置gif图片属性,利用67张png图片构建gif 1 let cgimagePropertiesDic = [kCGImagePropertyGIFDelayTime as String: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图片。 
    【图9】

    Gif图像展示

      iOS原生并不支持直接显示GIF图片,由前面的分析可知,GIF图片由一帧帧的单帧图片构成,所以只要实现GIF图片的分解,接下来就是多组图片显示的问题了。为大家介绍另外一种图片展现形式,即基于UIImageView展现GIF多帧图片。 
    经过对GIF图片展示思路的分析可以知道,在iOS下展现GIF分为两步:第一步分解GIF图片为单帧图片,第二步在iOS下展现多帧图片。UIImageView是一个用来展现图片的UI组件,不过它还有一些动画属性可以用来进行逐帧动画展现。 
    考虑到第一步GIF图片已经分解,所以这里把分解之后的67张图片先加载进来。 
    【图10】
      UIImageView多帧图像展示具体实现代码如下。

    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多帧图片展示动画。 
    【图11】
      本文选自《iOS动画——核心技术与案例实战》,点此链接可在博文视点官网查看此书。

    图片描述

      想及时获得更多精彩文章,可在微信中搜索“博文视点”或者扫描下方二维码并关注。
                                    图片描述

  • 相关阅读:
    wex5 实战 框架拓展之2 事件派发与data刷新
    wex5 实战 框架拓展之1 公共data组件(Data)
    wex5 实战 HeidiSQL 导入Excel数据
    wex5 实战 手指触屏插件 hammer的集成与优劣
    wex5 实战 登陆帐号更换与用户id一致性
    wex5 实战 用户点评与提交设计技巧
    wex5 实战 省市县三级联动与地址薄同步
    wex5 实战 wex5与js的组件关系与执行顺序(父子与先后)
    wex5 实战 单页模式下的多页面数据同步
    [BZOJ]4237: 稻草人
  • 原文地址:https://www.cnblogs.com/broadview/p/6375191.html
Copyright © 2011-2022 走看看