iOS 關于 GIF 圖片那點事

SamuelWithe 8年前發布 | 12K 次閱讀 iOS開發 移動開發

前言

前幾天我們項目組的群里提了這么一件事情:在我們的應用中存儲動態的GIF圖到相冊,保存的圖片變成了靜態圖片。而微博則能正確保存,可見這并不是一個技術不可實現的。前不久剛好看了蘋果關于 ImageIO 框架的指南,借著這個契機,我就去調研調研其中的原委。

使用UIImage讀取GIF圖片的不足

UIImage類在UIKit框架中,是我們最常使用的存儲圖片類。該類提供了可以使用圖片路徑或是圖片數據來實例化的類方法。UIImage類底層采用ImageIO框架來讀取圖片數據,下圖分別為 +imageWithContentsOfFile: 和 +imageWithData: 調用的堆棧。

image堆棧.png

從堆棧中我們可以看到圖片讀取的大致流程如下:

  1. 根據文件路徑或是數據生成 CGImageSource ;
  2. 然后調用 CGImageSourceCreateImageAtIndex 方法獲取一幀的圖片,類型為 CGImage ;
  3. 讓 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/

     

 本文由用戶 SamuelWithe 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!