iOS 關于 GIF 圖片那點事
前言
前幾天我們項目組的群里提了這么一件事情:在我們的應用中存儲動態的GIF圖到相冊,保存的圖片變成了靜態圖片。而微博則能正確保存,可見這并不是一個技術不可實現的。前不久剛好看了蘋果關于 ImageIO 框架的指南,借著這個契機,我就去調研調研其中的原委。
使用UIImage讀取GIF圖片的不足
UIImage類在UIKit框架中,是我們最常使用的存儲圖片類。該類提供了可以使用圖片路徑或是圖片數據來實例化的類方法。UIImage類底層采用ImageIO框架來讀取圖片數據,下圖分別為 +imageWithContentsOfFile: 和 +imageWithData: 調用的堆棧。
image堆棧.png
從堆棧中我們可以看到圖片讀取的大致流程如下:
- 根據文件路徑或是數據生成 CGImageSource ;
- 然后調用 CGImageSourceCreateImageAtIndex 方法獲取一幀的圖片,類型為 CGImage ;
- 讓 UIImage 對象持有該 CGImage 。
在流程的第一步生成的 CGImageSource ,仍然保留著GIF的全部信息。而在流程的第二步中出了問題。動態的Gif圖與靜態格式圖片不同,它包含有多張的靜態圖片。 CGImageSourceCreateImageAtIndex 只能返回索引值的圖片,丟失了其他的圖片信息。因此,我們只獲取到了其中的一幀圖片。出于好奇,我選擇了一張只有四幀完全不同的Gif圖,通過測試觀察, UIImage 獲取的是第一幀的圖片。既然我們不能用 UIImage 或 CGImage 來處理Gif圖,我們是否可以降級,采用 ImageIO 框架來處理呢。答案是肯定的。
使用 ImageIO 框架解析GIF圖片
我參考了 YYImage 框架的設計,定義了兩個類,分別為 JWGifDecoder 和 JWGifFrame , JWGifDecoder 類負責GIF圖片數據的解析,而 JWGifFrame 表示幀。兩者的頭文件如下:
#import
import "JWGifFrame.h"
@interface JWGifDecoder : NSObject
@property (nonatomic,readonly) NSData *data; /**
</code></pre>
#import
import
@interface JWGifFrame : NSObject
@property (nonatomic,assign) NSUIntegerindex; /**
</code></pre>
JWGifDecoder 內部使用 CGImageSource 來解析圖片數據。實例化時候,該類使用 CGImageSourceCreateWithData () 方法(這里的 c 語言方法忽略參數)一個 CGImageSource ,然后采用 CGImageSourceGetCount () 獲得其內部的圖片個數也就是幀數。而在生成幀對象時候,采用 CGImageSourceCopyPropertiesAtIndex () 方法獲得對應幀的屬性,采用 CGImageSourceCreateImageAtIndex() 方法得到圖片。
#import "JWGifDecoder.h"
import
@interface JWGifDecoder ()
{
CGImageSourceRef_source;
}
@end
@implementationJWGifDecoder
+(instancetype)decoderWithData:(NSData )data
{
if ( !data ) return nil;
JWGifDecoder decoder = [[JWGifDecoderalloc] init];
[decoder_decoderPrepareWithData:data];
return decoder;
}
- (void)dealloc
{
CFRelease(_source);
}
-(void)_decoderPrepareWithData:(NSData )data
{
_data = data;
_source = CGImageSourceCreateWithData((__bridgeCFDataRef)data, NULL);
_frameCount = CGImageSourceGetCount(_source);
CFDictionaryRefproperties = CGImageSourceCopyProperties(_source, NULL);
CFDictionaryRefgifProperties = CFDictionaryGetValue(properties, kCGImagePropertyGIFDictionary);
CFTypeRefloop = CFDictionaryGetValue(gifProperties, kCGImagePropertyGIFLoopCount);
if (loop) CFNumberGetValue(loop, kCFNumberNSIntegerType, &_loopCount);
CFRelease(properties);
}
-(JWGifFrame )frameAtIndex:(NSUInteger)index
{
if ( index >= _frameCount ) return nil;
JWGifFrame frame = [[JWGifFramealloc] init];
frame.index = index;
NSTimeIntervalduration = 0;
CFDictionaryRefframeProperties = CGImageSourceCopyPropertiesAtIndex(_source, index, NULL);
CFDictionaryRefgifFrameProperties = CFDictionaryGetValue(frameProperties, kCGImagePropertyGIFDictionary);
CFTypeRefdelayTime = CFDictionaryGetValue(gifFrameProperties, kCGImagePropertyGIFUnclampedDelayTime);
if(delayTime) CFNumberGetValue(delayTime, kCFNumberDoubleType, &duration);
CFRelease(frameProperties);
frame.duration = duration;
CGImageRefcgImage = CGImageSourceCreateImageAtIndex(_source, index, NULL);
UIImage image = [UIImageimageWithCGImage:cgImage];
frame.image = image;
CFRelease(cgImage);
return frame;
}
</code></pre>
保存GIF格式圖片至相冊
UIImage只會保留一幀的信息,而圖片的數據則具有GIF的所有數據。因此,相冊讀取GIF格式圖片有個原則:在保存圖片至相冊時,必須保存圖片的數據而不是UIImage對象。
IOS8以下 ALAssetsLibrary 框架處理相冊,在IOS8以上,則是采用 Photos 框架。在 這篇博客 中說在IOS8中使用 Photos 的方法會保存不了,由于沒有IOS8的系統的手機,我也無法做相關測試。目前測試我手上的IOS10系統的iphone6s,可以成功保存,保存的GIF圖片可以在微信中成功發送。保存相冊的代碼如下所示:
NSString *path = [[NSBundlemainBundle] pathForResource:@"niconiconi" ofType:@"gif"];
NSData *data = [NSDatadataWithContentsOfFile:path];
if ([UIDevicecurrentDevice].systemVersion.floatValue >= 9.0f) {
[[PHPhotoLibrarysharedPhotoLibrary] performChanges:^{
PHAssetResourceCreationOptions *options = [[PHAssetResourceCreationOptionsalloc] init];
[[PHAssetCreationRequestcreationRequestForAsset] addResourceWithType:PHAssetResourceTypePhotodata:dataoptions:options];
} completionHandler:^(BOOL success, NSError * _Nullableerror) {
NSLog(@"是否保存成功:%d",success);
}];
}
else {
ALAssetsLibrary *library = [[ALAssetsLibraryalloc] init];
[librarywriteImageDataToSavedPhotosAlbum:datametadata:nilcompletionBlock:^(NSURL *assetURL, NSError *error) {
}];
}
結語
ImageIO 框架給了我們更加強大的圖片處理能力,它可以處理 UIImage 無法應付的Gif格式的圖片。對于那些較高級的API無法處理的事情,可以試一試用更低層的框架看看是否進行處理。發現很多內容在蘋果的 Guide里都有說明,以后需要多多看看。
這篇文章中還有很多東西沒有講到,比如相冊讀取Gif圖片,又比如代碼將圖片保存成Gif圖片(這點在蘋果的Guide里有提到)等,以后再補充吧。
參考
來自:http://ios.jobbole.com/90423/