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

一、GIF概念和特點(diǎn)

  • 簡(jiǎn)介:
    GIF是一種常用于動(dòng)畫效果的圖片格式肋乍。
    GIF的原義是“圖像互換格式”鹅颊,是CompuServe公司在1987年開發(fā)的圖像文件格式,與常見的靜態(tài)圖像村塾格式JPG墓造、PNG類似堪伍,GIF是一種常用于動(dòng)態(tài)圖片的儲(chǔ)存的格式。
  • 特點(diǎn):可以通過單幀圖像合成GIF或?qū)IF盡心單幀分解觅闽。

二帝雇、GIF的使用場(chǎng)景

  • 1、GIF圖片分解為單幀圖片蛉拙;
  • 2尸闸、一系列單幀圖片合成GIF圖片;
  • 3孕锄、iOS系統(tǒng)上展示GIF動(dòng)畫效果吮廉。

三、GIF的使用

1畸肆、GIF分解單幀圖片

GIF圖片分解的過程
整個(gè)過程分為5個(gè)模塊宦芦、4個(gè)過程,分別如下轴脐。
  • (1)本地讀取GIF圖片调卑,將其轉(zhuǎn)換為NSData數(shù)據(jù)類型抡砂;
  • (2)將NSData作為ImageIO模塊的輸入;
  • (3)獲取ImageIO的輸出數(shù)據(jù):UIImage;
  • (4)將獲取到的UIImage數(shù)據(jù)存儲(chǔ)為JPG或PNG格式保存到本地恬涧。

在整個(gè)GIF圖片分解的過程中舀患,ImageIO是處理過程的核心部分。它負(fù)責(zé)對(duì)GIF文件格式進(jìn)行解析气破,并將解析之后的數(shù)據(jù)轉(zhuǎn)換為一幀幀圖片輸出聊浅。

代碼解析過程

  • 讀取GIF文件并將之轉(zhuǎn)換為NSData類型:
    /// 從本地讀取gif圖片 ==> 生成data數(shù)據(jù)
    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數(shù)據(jù)換成為ImageIO可以處理的數(shù)據(jù)類型,這里使用CGImageSourceRef實(shí)現(xiàn)碳锈。
    /// 將data圖片數(shù)據(jù)轉(zhuǎn)換成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類型數(shù)據(jù)顽冶。該方法有三個(gè)參數(shù),參數(shù)1位GIF原始數(shù)據(jù)售碳,參數(shù)2位GIF子幀中的序號(hào)(該序號(hào)從0開始)强重,參數(shù)3位GIF數(shù)據(jù)提取的一些選擇參數(shù),因?yàn)檫@里不是很常用贸人,所以設(shè)置為nil间景。

其中用UIImage類的實(shí)例化方法來實(shí)例化UIImage對(duì)象,該方法有三個(gè)參數(shù)艺智,參數(shù)1位需要構(gòu)建UIImage的內(nèi)容倘要,注意這里的內(nèi)容是CGImage類型,參數(shù)2為手機(jī)物理像素與手機(jī)和手機(jī)顯示分辨率的換算系數(shù)十拣,參數(shù)3標(biāo)明構(gòu)建的UIImage的圖像方向封拧。通過這個(gè)方法就可以在某種手機(jī)分辨率下構(gòu)建指定方向的圖像,當(dāng)然圖像的類型是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數(shù)據(jù)類型存儲(chǔ)為PNG格式的data數(shù)據(jù)類型泽西,并保存到沙盒中。

  • GIF圖片分解最終實(shí)現(xiàn)效果
GIF圖片分解最終實(shí)現(xiàn)效果

三缰趋、PNG圖片集合成GIF圖片

GIF圖片的合成三步驟:
  • (1)加載待處理的原始數(shù)據(jù)源捧杉;
  • (2)在Document目錄下構(gòu)建GIF文件;
  • (3)設(shè)置GIF文件屬性埠胖,利用ImageIO編碼GIF文件糠溜。

代碼實(shí)現(xiàn):

// 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.創(chuàng)建Gif圖在Document中的保存路徑
    func creatGifPath() -> String {
        let docs = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
        let gifPath = docs[0] as String + "/refresh.gif"
        print(gifPath)
        return gifPath
    }
    
    // 3.設(shè)置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)
        
        // 設(shè)置gif圖片屬性
        // 設(shè)置每幀之間播放的時(shí)間0.1
        let delayTime = [kCGImagePropertyGIFDelayTime as String:0.1]
        let destDic   = [kCGImagePropertyGIFDictionary as String:delayTime]
        // 依次為gif圖像對(duì)象添加每一幀屬性
        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)         // 設(shè)置圖片的顏色深度
        propertiesDic.setValue(1, forKey: kCGImagePropertyGIFLoopCount as String)   // 設(shè)置Gif執(zhí)行次數(shù)
        
        let gitDestDic = [kCGImagePropertyGIFDictionary as String:propertiesDic]    // 為gif圖像設(shè)置屬性
        CGImageDestinationSetProperties(destion!, gitDestDic as CFDictionary?)
        CGImageDestinationFinalize(destion!)
        return true
    }

需要導(dǎo)入 import MobileCoreServices 框架直撤;

其中CGImageDestinationCreateWithURL方法的作用是創(chuàng)建一個(gè)圖片的目標(biāo)對(duì)象非竿,四個(gè)參數(shù)分別代表圖片的URL地址、圖片類型谋竖、圖片的幀數(shù)和配置信息等红柱。


合成后的效果圖

iOS播放Gif的方式介紹

這里提供五種方式:

  • 使用DispatchSource創(chuàng)建定時(shí)器播放gif圖(推薦)承匣;
  • 使用UIImageView直接展示;
  • 基于Timer定時(shí)器的逐幀動(dòng)畫效果锤悄;
  • 基于CADisplaylink的逐幀動(dòng)畫效果韧骗;
  • 使用WebView直接加載gif圖。
    let kImagesCount: NSInteger = 24   // 圖片集個(gè)數(shù)
    
    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())
        
        // 方法二:使用定時(shí)器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創(chuàng)建定時(shí)器零聚,播放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!)
    }

   /// 釋放定時(shí)器
   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()
        }
    }
    
    // 方法二:使用定時(shí)器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圖袍暴,進(jìn)行播放
    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創(chuàng)建定時(shí)器隶症,播放Gif圖
    /// 根據(jù)DispatchSource創(chuàng)建定時(shí)器政模,播放Gif圖
    ///
    /// - Parameter url: 資源路徑(網(wǎng)絡(luò)或本地)
    private func showGifByDispatchSource(url: String) {
        guard url.count > 0 else {
            return
        }
        var data: Data?
        if self.checkUrlIsNetWorkData(urlString: url) {   // 網(wǎng)絡(luò)資源
            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)
        }
    }
    
    /// 驗(yàn)證url資源是網(wǎng)絡(luò)還是本地資源
    ///
    /// - Parameter urlString: 資源路徑
    /// - Returns: 返回 true 表示為網(wǎng)絡(luò)資源,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
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末蚂会,一起剝皮案震驚了整個(gè)濱河市淋样,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌胁住,老刑警劉巖趁猴,帶你破解...
    沈念sama閱讀 219,427評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異彪见,居然都是意外死亡儡司,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門企巢,熙熙樓的掌柜王于貴愁眉苦臉地迎上來枫慷,“玉大人,你說我怎么就攤上這事浪规。” “怎么了探孝?”我有些...
    開封第一講書人閱讀 165,747評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵笋婿,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我顿颅,道長(zhǎng)缸濒,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,939評(píng)論 1 295
  • 正文 為了忘掉前任粱腻,我火速辦了婚禮庇配,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘绍些。我一直安慰自己捞慌,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,955評(píng)論 6 392
  • 文/花漫 我一把揭開白布柬批。 她就那樣靜靜地躺著啸澡,像睡著了一般袖订。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上嗅虏,一...
    開封第一講書人閱讀 51,737評(píng)論 1 305
  • 那天洛姑,我揣著相機(jī)與錄音,去河邊找鬼皮服。 笑死楞艾,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的龄广。 我是一名探鬼主播产徊,決...
    沈念sama閱讀 40,448評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼蜀细!你這毒婦竟也來了舟铜?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,352評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤奠衔,失蹤者是張志新(化名)和其女友劉穎谆刨,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體归斤,經(jīng)...
    沈念sama閱讀 45,834評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡痊夭,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,992評(píng)論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了脏里。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片她我。...
    茶點(diǎn)故事閱讀 40,133評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖迫横,靈堂內(nèi)的尸體忽然破棺而出番舆,到底是詐尸還是另有隱情,我是刑警寧澤矾踱,帶...
    沈念sama閱讀 35,815評(píng)論 5 346
  • 正文 年R本政府宣布恨狈,位于F島的核電站,受9級(jí)特大地震影響呛讲,放射性物質(zhì)發(fā)生泄漏禾怠。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,477評(píng)論 3 331
  • 文/蒙蒙 一贝搁、第九天 我趴在偏房一處隱蔽的房頂上張望吗氏。 院中可真熱鬧,春花似錦雷逆、人聲如沸弦讽。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽坦袍。三九已至十厢,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間捂齐,已是汗流浹背蛮放。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留奠宜,地道東北人包颁。 一個(gè)月前我還...
    沈念sama閱讀 48,398評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像压真,于是被迫代替她去往敵國(guó)和親娩嚼。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,077評(píng)論 2 355