Gif 動圖
在macOS
與iOS
平臺上都是被廣泛使用的一種圖片資源;但在這兩個平臺上關于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呢? 這主要看具體的業務需求場景, 筆者遇到的幾個實際需求場景如下:
- 需要控制Gif的播放次數.
- 需要提取Gif的某一幀圖片.
- 需要給Gif 添加水印效果(需要將水印加到Gif的每一幀圖片上).
- 獲取Gif的內部圖片信息.
由于NSImageView
僅提供了一個animates
布爾值來控制是否動畫顯示Gif圖片,無法指定播放次數,同時在iOS平臺中,UIImageview
沒有animates
這個屬性支持Gif格式圖像,通常需要引入第三方庫實現對Gif的支持,通過了解ImageIO,我們可以更容易理解第三方庫是如何進行Gif處理的,這將有助于我們進行定制功能的開發或者有效的Bug處理.
0x03: 動手解析Gif
-
示例Gif 資源:
Gif示例資源 -
創建測試項目工程:
測試項目工程 - 核心代碼:
/** 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];
-
示例代碼運行效果:
代碼運行效果
這里主要用到了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
}
}
-
動畫效果:
使用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數組
賦值給UIImageView
的animationImages
屬性實現Gif動效,權當閱讀后的練習,就不給出具體代碼了...
本文僅是關于ImageIO
的一個簡單介紹,通過解析Gif圖像數據來揭開這個框架的冰山一角,后續有機會再繼續補充.
0x05: One more thing....
- 使用
NSTimer
時,因為repeats
設置的為true
, 需要注意內存釋放
問題. - 如需要實現
Gif水印
,則需要使用CGImageDestination
將數據寫回到指定路徑.
示例代碼Github地址