Swift4.0-- GIF的分解、合成和播放

一、GIF概念和特点

  • 简介:
    GIF是一种常用于动画效果的图片格式。
    GIF的原义是“图像互换格式”,是CompuServe公司在1987年开发的图像文件格式,与常见的静态图像村塾格式JPG、PNG类似,GIF是一种常用于动态图片的储存的格式。
  • 特点:可以通过单帧图像合成GIF或对GIF尽心单帧分解。

二、GIF的使用场景

  • 1、GIF图片分解为单帧图片;
  • 2、一系列单帧图片合成GIF图片;
  • 3、iOS系统上展示GIF动画效果。

三、GIF的使用

1、GIF分解单帧图片

GIF图片分解的过程
整个过程分为5个模块、4个过程,分别如下。
  • (1)本地读取GIF图片,将其转换为NSData数据类型;
  • (2)将NSData作为ImageIO模块的输入;
  • (3)获取ImageIO的输出数据:UIImage;
  • (4)将获取到的UIImage数据存储为JPG或PNG格式保存到本地。

在整个GIF图片分解的过程中,ImageIO是处理过程的核心部分。它负责对GIF文件格式进行解析,并将解析之后的数据转换为一帧帧图片输出。

代码解析过程

  • 读取GIF文件并将之转换为NSData类型:
    /// 从本地读取gif图片 ==> 生成data数据
    func loadGifInLocal(gifName: String?) -> Data? {
        guard gifName != nil else {
            return nil
        }
        
        let gifPath = Bundle.main.path(forResource: gifName, ofType: "gif")
        guard gifPath != nil else {
            print("文件名为" + gifName! + "的gif图不存在!")
            return nil
        }
        var gifData: Data = Data.init()
        do {
            gifData = try Data.init(contentsOf: URL.init(fileURLWithPath: gifPath!))
        } catch {
            print(error)
        }
        return gifData
    }
  • 利用ImageIO框架,遍历所有GIF子帧。需要注意的是使用ImageIO必须吧读取到的NSData数据换成为ImageIO可以处理的数据类型,这里使用CGImageSourceRef实现。
    /// 将data图片数据转换成image
    func createImage(gifData: Data?) -> NSArray? {
        guard gifData != nil else {
            return nil
        }
        
        let gifDataSource: CGImageSource = CGImageSourceCreateWithData(gifData! as CFData, nil)!
        let gifImageCount: NSInteger = CGImageSourceGetCount(gifDataSource)
        let images: NSMutableArray   = NSMutableArray.init()
        for index in 0...gifImageCount-1 {
            let imageref: CGImage? = CGImageSourceCreateImageAtIndex(gifDataSource, index, nil)
            let image: UIImage = UIImage.init(cgImage: imageref!, scale: UIScreen.main.scale, orientation: UIImageOrientation.up)
            images.add(image)
        }
        return images
    }

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

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

  • 保存UIImage图像为png格式的图像到沙盒中
    // 将image图片保存到本地沙盒
    func saveImagesToLocal(imageArray images: Array<UIImage>?) -> () {
        guard images?.count != 0 else {
            return
        }
        
        for image in images! {
            let index = images?.index(of: image)
            let imageData: Data = UIImagePNGRepresentation(image)!
            let docs = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
            let documentsDirectory: String = docs[0] as String
            let imagePath = documentsDirectory + "/shopping\(index ?? 0)" + ".png"
            try? imageData.write(to: URL.init(fileURLWithPath: imagePath), options: [.atomic])
            print(imagePath)
        }
    }

其中UIImagePNGRepresentation方法将UImage数据类型存储为PNG格式的data数据类型,并保存到沙盒中。

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

三、PNG图片集合成GIF图片

GIF图片的合成三步骤:
  • (1)加载待处理的原始数据源;
  • (2)在Document目录下构建GIF文件;
  • (3)设置GIF文件属性,利用ImageIO编码GIF文件。

代码实现:

// 1. 加载本地图片
    func loadImages() -> NSArray {
        let imageArray: NSMutableArray = NSMutableArray()
        for i in 1...25 {
            let image = UIImage.init(named: "refresh_gif\(i).png")
            if image != nil {
                imageArray.add(image!)
            }
        }
        return imageArray
    }
    
    // 2.创建Gif图在Document中的保存路径
    func creatGifPath() -> String {
        let docs = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
        let gifPath = docs[0] as String + "/refresh.gif"
        print(gifPath)
        return gifPath
    }
    
    // 3.设置GIF属性,利用ImageIO编码GIF文件
    func saveGifToDocument(imageArray images: NSArray, _ gifPath: String) -> Bool {
        guard images.count > 0 &&
             gifPath.characters.count > 0 else {
            return false
        }
        let url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, gifPath as CFString, .cfurlposixPathStyle, false)
        let destion = CGImageDestinationCreateWithURL(url!, kUTTypeGIF, images.count, nil)
        
        // 设置gif图片属性
        // 设置每帧之间播放的时间0.1
        let delayTime = [kCGImagePropertyGIFDelayTime as String:0.1]
        let destDic   = [kCGImagePropertyGIFDictionary as String:delayTime]
        // 依次为gif图像对象添加每一帧属性
        for image in images {
            CGImageDestinationAddImage(destion!, (image as AnyObject).cgImage!!, destDic as CFDictionary?)
        }
        
        let propertiesDic: NSMutableDictionary = NSMutableDictionary()
        propertiesDic.setValue(kCGImagePropertyColorModelRGB, forKey: kCGImagePropertyColorModel as String)
        propertiesDic.setValue(16, forKey: kCGImagePropertyDepth as String)         // 设置图片的颜色深度
        propertiesDic.setValue(1, forKey: kCGImagePropertyGIFLoopCount as String)   // 设置Gif执行次数
        
        let gitDestDic = [kCGImagePropertyGIFDictionary as String:propertiesDic]    // 为gif图像设置属性
        CGImageDestinationSetProperties(destion!, gitDestDic as CFDictionary?)
        CGImageDestinationFinalize(destion!)
        return true
    }

需要导入 import MobileCoreServices 框架;

其中CGImageDestinationCreateWithURL方法的作用是创建一个图片的目标对象,四个参数分别代表图片的URL地址、图片类型、图片的帧数和配置信息等。


合成后的效果图

iOS播放Gif的方式介绍

这里提供五种方式:

  • 使用DispatchSource创建定时器播放gif图(推荐);
  • 使用UIImageView直接展示;
  • 基于Timer定时器的逐帧动画效果;
  • 基于CADisplaylink的逐帧动画效果;
  • 使用WebView直接加载gif图。
    let kImagesCount: NSInteger = 24   // 图片集个数
    
    lazy var imageView: UIImageView = {
        let imageView: UIImageView = UIImageView()
        return imageView
    }()
    
    var timer: Timer?
    var displayLink: CADisplayLink?
    
    var index: NSInteger = 1 
    override func viewDidLoad() {
        super.viewDidLoad() 
        
        self.view.addSubview(imageView)
        imageView.center = self.view.center;
        imageView.bounds.size = CGSize.init(width: self.view.bounds.size.width, height: 50)
        
        // 方法一:使用UIimageView播放gif图片
//        self.showGifByUIImageView(images: self.loadImages())
        
        // 方法二:使用定时器Timer播放gif图片
//        self.showGifByTimer()
        
        // 方法三:使用CADisplayLink播放gif图片
//        self.showGifByCADisplayLink()

        // 方式四:用WebView直接加载gif图
//        self.view.addSubview(self.webView)
//        self.webView.frame = self.view.bounds
//        self.showGifByWebView(gifName: "shopping")
        
        // 方式五:用DispatchSource创建定时器,播放Gif图
        imageView.frame = self.view.bounds
        let url = Bundle.main.path(forResource: "shopping.gif", ofType: nil)
        guard url != nil else {
            return
        }
        self.showGifByDispatchSource(url: url!)
    }

   /// 释放定时器
   deinit {
    timer?.invalidate()
    timer = nil
   }



// MARK: - 播放Gif图
extension ViewController {
    
    // 方法一:使用UIimageView播放gif图片
    func showGifByUIImageView(images: NSArray) -> () {
        if images.count > 0 {
            self.imageView.animationImages = images as? [UIImage]
            self.imageView.animationDuration = 2.0
            self.imageView.animationRepeatCount = 300
            self.imageView.startAnimating()
        }
    }
    
    // 方法二:使用定时器Timer播放gif图片
    func showGifByTimer() -> () {
        timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(refreshImageView), userInfo: nil, repeats: true)
        RunLoop.current.add(timer!, forMode: RunLoopMode.commonModes)
    }
    
    // 方法三:使用CADisplayLink播放gif图片
    func showGifByCADisplayLink() -> () {
        displayLink = CADisplayLink.init(target: self, selector: #selector(refreshImageView))
        displayLink?.frameInterval = 6
        displayLink?.add(to: RunLoop.current, forMode: RunLoopMode.defaultRunLoopMode)
    }
     
    func loadImages() -> NSArray {
        let imageArray: NSMutableArray = NSMutableArray()
        for i in 1...kImagesCount {
            let image = UIImage.init(named: "refresh_gif\(i).png")
            if image != nil {
                imageArray.add(image!)
            }
        }
        return imageArray
    }
    
    // 方式四:用WebView直接加载gif图,进行播放
    func showGifByWebView(gifName: String) -> () {
        guard gifName.characters.count > 0 else {
            return
        }
        
        let path: String = Bundle.main.path(forResource: gifName, ofType: "gif") ?? ""
        let gifData = NSData.dataWithContentsOfMappedFile(path)
        if gifData != nil {
            self.webView.load(gifData as! Data, mimeType: "image/gif", textEncodingName: "", baseURL: NSURL() as URL)
        } else {
            print("GIF图名为:" + gifName + "的图不存在!")
        }
    }    

    // 方式五:用DispatchSource创建定时器,播放Gif图
    /// 根据DispatchSource创建定时器,播放Gif图
    ///
    /// - Parameter url: 资源路径(网络或本地)
    private func showGifByDispatchSource(url: String) {
        guard url.count > 0 else {
            return
        }
        var data: Data?
        if self.checkUrlIsNetWorkData(urlString: url) {   // 网络资源
            data = try? Data.init(contentsOf: URL.init(string: url)!)
        } else {                                          // 本地资源
            data = try? NSData.init(contentsOfFile: url) as Data
        }
        guard let imageData = data else { return }
        let type = GifImageOperation.checkDataType(data: imageData)
        if type == .gif {
        let gifView = GifImageOperation.init(frame: imageView.bounds, gifData: imageData)
            gifView.startAnimation()
            imageView.addSubview(gifView)
        } else {
            imageView.image = UIImage.init(data: imageData)
        }
    }
    
    /// 验证url资源是网络还是本地资源
    ///
    /// - Parameter urlString: 资源路径
    /// - Returns: 返回 true 表示为网络资源,false 为本地资源
    private func checkUrlIsNetWorkData(urlString: String?) -> Bool {
        guard urlString != nil else { return false }
        let regex = "http(s)?:\\/\\/([\\w-]+\\.)+[\\w-]+(\\/[\\w- .\\/?%&=]*)?"
        let predicate = NSPredicate.init(format: "SELF MATCHES %@", regex)
        return predicate.evaluate(with:urlString)
    }
    
    @objc func refreshImageView() -> () {
        if index < 1 || index > kImagesCount {
            index = 1
        }
        let image: UIImage = UIImage.init(named: "refresh_gif\(index).png")!
        imageView.image = image
        index += 1
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 195,980评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,422评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,130评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,553评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,408评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,326评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,720评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,373评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,678评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,722评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,486评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,335评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,738评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,283评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,692评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,893评论 2 335