前言
前幾天我們專案組的群裡提了這麼一件事情:在我們的應用中儲存動態的GIF圖到相簿,儲存的圖片變成了靜態圖片。而微博則能正確儲存,可見這並不是一個技術不可實現的。前不久剛好看了蘋果關於ImageIO
框架的指南,藉著這個契機,我就去調研調研其中的原委。
使用UIImage讀取GIF圖片的不足
UIImage類在UIKit框架中,是我們最常使用的儲存圖片類。該類提供了可以使用圖片路徑或是圖片資料來例項化的類方法。UIImage類底層採用ImageIO框架來讀取圖片資料,下圖分別為+imageWithContentsOfFile:
和+imageWithData:
呼叫的堆疊。
從堆疊中我們可以看到圖片讀取的大致流程如下:
- 根據檔案路徑或是資料生成
CGImageSource
; - 然後呼叫
CGImageSourceCreateImageAtIndex
方法獲取一幀的圖片,型別為CGImage
; - 讓
UIImage
物件持有該CGImage
。
在流程的第一步生成的CGImageSource
,仍然保留著GIF的全部資訊。而在流程的第二步中出了問題。動態的Gif圖與靜態格式圖片不同,它包含有多張的靜態圖片。CGImageSourceCreateImageAtIndex
只能返回索引值的圖片,丟失了其他的圖片資訊。因此,我們只獲取到了其中的一幀圖片。出於好奇,我選擇了一張只有四幀完全不同的Gif圖,通過測試觀察,UIImage
獲取的是第一幀的圖片。既然我們不能用UIImage
或CGImage
來處理Gif圖,我們是否可以降級,採用ImageIO
框架來處理呢。答案是肯定的。
使用 ImageIO 框架解析GIF圖片
我參考了YYImage
框架的設計,定義了兩個類,分別為JWGifDecoder
和JWGifFrame
,JWGifDecoder
類負責GIF圖片資料的解析,而JWGifFrame
表示幀。兩者的標頭檔案如下:
1 2 3 4 5 6 |
#import #import "JWGifFrame.h" @interface JWGifDecoder : NSObject @property (nonatomic,readonly) NSData *data; /** |
1 2 3 4 5 6 |
#import #import @interface JWGifFrame : NSObject @property (nonatomic,assign) NSUInteger index; /** |
JWGifDecoder
內部使用CGImageSource
來解析圖片資料。例項化時候,該類使用CGImageSourceCreateWithData ()
方法(這裡的c
語言方法忽略引數)一個CGImageSource
,然後採用CGImageSourceGetCount ()
獲得其內部的圖片個數也就是幀數。而在生成幀物件時候,採用CGImageSourceCopyPropertiesAtIndex ()
方法獲得對應幀的屬性,採用CGImageSourceCreateImageAtIndex()
方法得到圖片。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
#import "JWGifDecoder.h" #import @interface JWGifDecoder () { CGImageSourceRef _source; } @end @implementation JWGifDecoder +(instancetype)decoderWithData:(NSData *)data { if ( !data ) return nil; JWGifDecoder *decoder = [[JWGifDecoder alloc] init]; [decoder _decoderPrepareWithData:data]; return decoder; } - (void)dealloc { CFRelease(_source); } -(void)_decoderPrepareWithData:(NSData *)data { _data = data; _source = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL); _frameCount = CGImageSourceGetCount(_source); CFDictionaryRef properties = CGImageSourceCopyProperties(_source, NULL); CFDictionaryRef gifProperties = CFDictionaryGetValue(properties, kCGImagePropertyGIFDictionary); CFTypeRef loop = CFDictionaryGetValue(gifProperties, kCGImagePropertyGIFLoopCount); if (loop) CFNumberGetValue(loop, kCFNumberNSIntegerType, &_loopCount); CFRelease(properties); } -(JWGifFrame *)frameAtIndex:(NSUInteger)index { if ( index >= _frameCount ) return nil; JWGifFrame *frame = [[JWGifFrame alloc] init]; frame.index = index; NSTimeInterval duration = 0; CFDictionaryRef frameProperties = CGImageSourceCopyPropertiesAtIndex(_source, index, NULL); CFDictionaryRef gifFrameProperties = CFDictionaryGetValue(frameProperties, kCGImagePropertyGIFDictionary); CFTypeRef delayTime = CFDictionaryGetValue(gifFrameProperties, kCGImagePropertyGIFUnclampedDelayTime); if(delayTime) CFNumberGetValue(delayTime, kCFNumberDoubleType, &duration); CFRelease(frameProperties); frame.duration = duration; CGImageRef cgImage = CGImageSourceCreateImageAtIndex(_source, index, NULL); UIImage *image = [UIImage imageWithCGImage:cgImage]; frame.image = image; CFRelease(cgImage); return frame; } |
儲存GIF格式圖片至相簿
UIImage只會保留一幀的資訊,而圖片的資料則具有GIF的所有資料。因此,相簿讀取GIF格式圖片有個原則:在儲存圖片至相簿時,必須儲存圖片的資料而不是UIImage物件。
IOS8以下ALAssetsLibrary
框架處理相簿,在IOS8以上,則是採用Photos
框架。在這篇部落格中說在IOS8中使用Photos
的方法會儲存不了,由於沒有IOS8的系統的手機,我也無法做相關測試。目前測試我手上的IOS10系統的iphone6s,可以成功儲存,儲存的GIF圖片可以在微信中成功傳送。儲存相簿的程式碼如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
NSString *path = [[NSBundle mainBundle] pathForResource:@"niconiconi" ofType:@"gif"]; NSData *data = [NSData dataWithContentsOfFile:path]; if ([UIDevice currentDevice].systemVersion.floatValue >= 9.0f) { [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{ PHAssetResourceCreationOptions *options = [[PHAssetResourceCreationOptions alloc] init]; [[PHAssetCreationRequest creationRequestForAsset] addResourceWithType:PHAssetResourceTypePhoto data:data options:options]; } completionHandler:^(BOOL success, NSError * _Nullable error) { NSLog(@"是否儲存成功:%d",success); }]; } else { ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init]; [library writeImageDataToSavedPhotosAlbum:data metadata:nil completionBlock:^(NSURL *assetURL, NSError *error) { }]; } |
結語
ImageIO
框架給了我們更加強大的圖片處理能力,它可以處理UIImage
無法應付的Gif格式的圖片。對於那些較高階的API無法處理的事情,可以試一試用更低層的框架看看是否進行處理。發現很多內容在蘋果的 Guide裡都有說明,以後需要多多看看。
這篇文章中還有很多東西沒有講到,比如相簿讀取Gif圖片,又比如程式碼將圖片儲存成Gif圖片(這點在蘋果的Guide裡有提到)等,以後再補充吧。
參考
打賞支援我寫出更多好文章,謝謝!
打賞作者
打賞支援我寫出更多好文章,謝謝!
任選一種支付方式