Mac開發跬步積累(四):ImageIO解析Gif 圖像數據

圖片來自網絡

Gif 動圖macOSiOS平臺上都是被廣泛使用的一種圖片資源;但在這兩個平臺上關于Gif動圖的支持卻是完全不同的效果: NSImageView (macOS)默認支持Gif格式的圖片資源,而UIImageView(iOS)默認是無法動畫顯示Gif格式的圖片資源

0x00: 什么是Gif 圖片?

GIF 分為靜態GIF和動畫GIF兩種,擴展名為.gif,是一種壓縮位圖格式,支持透明背景圖像,適用于多種操作系統,“體型”很小,網上很多小動畫都是GIF格式。其實GIF是將多幅圖像保存為一個圖像文件,從而形成動畫,最常見的就是通過一幀幀的動畫串聯起來的搞笑gif圖,所以歸根到底GIF仍然是圖片文件格式 ---引自 <<百度百科>>

這里可以看到Gif 是保存了多幅圖像的一個圖像文件,有了這個基礎認識,我們就可以使用代碼來解析Gif圖像了.

0x01: 關于ImageIO 框架

iOS4.0+macOS1.08+之后蘋果提供了ImageIO框架,它是一個圖像管理框架,提供了對圖像的讀寫,管理顏色空間,圖像格式以及訪問圖像元數據的功能.這個框架中提供了下面五個模塊:

ImageIO.CGImageDestination
ImageIO.CGImageMetadata
ImageIO.CGImageProperties
ImageIO.CGImageSource
ImageIO.ImageIOBase

其中解析數據我們主要使用CGImageSource這個模塊.

0x02: 為什么需要手動解析Gif?

既然在macOS中的NSImageView控件默認支持播放Gif圖像資源,為什么我們還需要自己解析Gif呢? 這主要看具體的業務需求場景, 筆者遇到的幾個實際需求場景如下:

  1. 需要控制Gif的播放次數.
  2. 需要提取Gif的某一幀圖片.
  3. 需要給Gif 添加水印效果(需要將水印加到Gif的每一幀圖片上).
  4. 獲取Gif的內部圖片信息.

由于NSImageView僅提供了一個animates布爾值來控制是否動畫顯示Gif圖片,無法指定播放次數,同時在iOS平臺中,UIImageview沒有animates這個屬性支持Gif格式圖像,通常需要引入第三方庫實現對Gif的支持,通過了解ImageIO,我們可以更容易理解第三方庫是如何進行Gif處理的,這將有助于我們進行定制功能的開發或者有效的Bug處理.

0x03: 動手解析Gif

  1. 示例Gif 資源:


    Gif示例資源
  2. 創建測試項目工程:


    測試項目工程
  3. 核心代碼:
       /** 1. 獲取gif 資源的路徑 */
        guard let gifPath = Bundle.main.pathForImageResource(NSImage.Name.init("timg.gif"))else{return}
        /** 2. 讀取gif 圖片資源元數據 */
        guard let gifData = NSData(contentsOfFile: gifPath) else {return}
        /** 3. 根據圖片元數據生成 cfImageSourceRef (包含了gif資源的內部數據信息) */
        guard let imageSourceRef = CGImageSourceCreateWithData(gifData, nil) else {return}
        /** 4. 獲取gif 中的圖片個數 */
        let imageCount = CGImageSourceGetCount(imageSourceRef)
        /** 5. 創建數組,用于存放轉換后的NSImage */
        var imageArray = [NSImage]();
        for  i in 0 ..< imageCount {
            /** 6. 獲取CGImage 資源  */
            guard let cgImageRef =  CGImageSourceCreateImageAtIndex(imageSourceRef, i, nil) else {continue}
            /** 7. 根據CGImage 創建NSImage  */
            let image =  NSImage(cgImage: cgImageRef, size: CGSize(width: cgImageRef.width, height: cgImageRef.height))
            /** 8. 將NSImage 添加的數組中 */
            imageArray.append(image)
        }
        /** 顯示某一幀圖片 (此示例中顯示為100 ,圖片總數為104)*/
        imageView.image = imageArray[100];
  1. 示例代碼運行效果:


    代碼運行效果

這里主要用到了CGImageSource中的幾個函數:

  • CGImageSourceCreateWithData(): 根據gif數據創建CGImageSource;
  • CGImageSourceGetCount(): 獲取gif中包含的圖片總數;
  • CGImageSourceCreateImageAtIndex(): 根據自定幀數創建一個CGImage對象;

到這里我們已經獲取到Gif數據中的圖片個數,并將每幀圖片都保存到我們自己的一個數組中了,這樣我們就可以使用NSTimer來實現動畫效果了.

/ ** 懶加載定時器 */ 
 lazy var animTimer = Timer(timeInterval: 0.1, target: self, selector: #selector(starGifAnimated), userInfo: nil, repeats: true)
 
/** 添加播放記錄索引 */ 
 var playIndex = 0

/** 動畫執行方法 */
 @objc func starGifAnimated() {
        imageView.image = imageArray[playIndex]
        playIndex += 1
        if playIndex == imageArray.count {
            playIndex = 0
        }
    }
  1. 動畫效果:


    使用NSTimer實現動效

細心的朋友可能會看到這里的動畫是通過設置NSTimer每個0.1秒(這個值是隨便寫的)來重復執行的,那么我們如何來獲取一個正確的Gif播放時長呢?
其實在CGImageSource中我們可以使用下面這個函數獲取Gif每幀圖片的播放時長;

  • CGImageSourceCopyPropertiesAtIndex(): 獲取指定幀的屬性值 -> 返回一個字典;
    kCGImagePropertyGIFDictionary: // 獲取gif 信息的key
    kCGImagePropertyGIFDelayTime: // 獲取gif一幀時長的key
            /** 9. 獲取指定幀的屬性信息 */
            guard let properties =  CGImageSourceCopyPropertiesAtIndex(imageSourceRef, i, nil) as? NSDictionary else {continue}
            /** 10. 獲取指定幀的gif信息字典 */
            guard let gifDictInfo = properties[kCGImagePropertyGIFDictionary] as? NSDictionary else {continue}
            /** 11. 獲取一幀的時長 */
            guard let duration = gifDictInfo[kCGImagePropertyGIFDelayTime] as? NSNumber else {continue}
            /** 累加計算總時長 */
            playTotalTime += duration.doubleValue

最后,我們根據總時長和總的圖片數就可以計算出NSTimer的執行間隔了.

0x04: 小結

我們通過ImageIO 實現了手動解析Gif圖像資源,并使用NSTimer循環所有圖片數組實現Gif 的動畫效果.通過這個例子希望能夠幫助大家進一步的了解在macOS中NSImageView 和iOS平臺中UIImageView如何更靈活的使用Gif圖像資源.
本例中只是macOS上的效果,對于iOS有興趣的朋友可以試著將解析后的NSImage數組賦值給UIImageViewanimationImages屬性實現Gif動效,權當閱讀后的練習,就不給出具體代碼了...
本文僅是關于ImageIO的一個簡單介紹,通過解析Gif圖像數據來揭開這個框架的冰山一角,后續有機會再繼續補充.

0x05: One more thing....

  • 使用NSTimer 時,因為repeats設置的為true, 需要注意內存釋放問題.
  • 如需要實現Gif水印,則需要使用CGImageDestination將數據寫回到指定路徑.
    示例代碼Github地址
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,505評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,556評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,463評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,009評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,778評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,218評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,281評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,436評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,969評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,795評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,993評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,537評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,229評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,659評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,917評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,687評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,990評論 2 374

推薦閱讀更多精彩內容