天天看點

簡單分析YYWebImage 是如何判斷圖檔格式的原理 (附上SDWebImage)

導語

目前關于網絡下載下傳圖檔的架構,除了十分熱門的SDWebImage之外還有很多,比如PINRemoteImage、FlyImage等許許多多優秀的架構。這篇文章主要講的YYWebImage當然也是其中的一員。寫這篇文章的主要目的是希望能和大家一起探讨一下網絡下載下傳圖檔是如何來判定格式的原理

前言

YYWebImage的作者是ibireme,Git網址:https://github.com/ibireme/YYWebImage

此網站上十分詳細的寫了如何安裝以及使用YYWebImage

SDWebImage Git網址:https://github.com/rs/SDWebImage

在閑暇之餘,研究一下大神寫的代碼,分析一下他們是如何實作判斷圖檔格式的代碼。

圖檔的格式

首先,為大家普及一下圖檔的格式(PS:不知道的可以看看,知道的就跳過哈)

圖檔常見的格式有:GIF 、PNG、JPEG、BMP、TIFF 等。

其實每一個圖檔格式都有對應的十六進制資料(PS:當然十六進制也是從二進制轉換過來的),也可以說就是這些十六進制資料組成了一張圖檔,然後再通過計算機内部的渲染等一系列算法進而顯示了一張圖檔,而往往前面的4~8個位元組往往都代表了這張圖檔的格式

如:

89 50 4E 47 0D 0A 1A 0A 00 00 …………

前面的4個位元組(89 50 4E 47)的ASCII 對應的就是 ‘.’ ‘P’ ‘N’ ‘G’;

再如:

47 49 46 38 39 61 64 00 …….

前面的4個位元組的ASCII 對應的就是’G’ ‘I’ ‘F’ ‘8’

(PS:為啥上傳不了圖檔,要不然截個圖超省事…….)

正文

接下來咱們就一起看看YYWebImage

在YYWebImage的 YYWebImageOperation.m 中找到了這麼一段代碼

YYImageType imageType = YYImageDetectType((__bridge CFDataRef)self.data);
                switch (imageType) {
                    case YYImageTypeJPEG:
                    case YYImageTypeGIF:
                    case YYImageTypePNG:
                    case YYImageTypeWebP: { // save to disk cache
                        if (!hasAnimation) {
                            if (imageType == YYImageTypeGIF ||
                                imageType == YYImageTypeWebP) {
                                self.data = nil; // clear the data, re-encode for disk cache
                            }
                        }
                    } break;
                    default: {
                        self.data = nil; // clear the data, re-encode for disk cache
                    } break;
                }
           

這是一段已經要傳回出圖檔格式了,咱們點進YYImageDetectType這個方法看看,裡面是如何實作的

YYImageType YYImageDetectType(CFDataRef data) {
    if (!data) return YYImageTypeUnknown;
    uint64_t length = CFDataGetLength(data);
    if (length < ) return YYImageTypeUnknown;

    const char *bytes = (char *)CFDataGetBytePtr(data);

    uint32_t magic4 = *((uint32_t *)bytes);
    switch (magic4) {
        case YY_FOUR_CC(, , , ): { // big endian TIFF
            return YYImageTypeTIFF;
        } break;
        case YY_FOUR_CC(, , , ): { // little endian TIFF
            return YYImageTypeTIFF;
        } break;

        case YY_FOUR_CC(, , , ): { // ICO
            return YYImageTypeICO;
        } break;
 ......
           

正如我們預料的那樣,YYWebImage同樣也是用每個圖檔的開頭前4個或前8個位元組的資料來判斷的

接下來進一步分析一下:

這個方法很長我就截取了一部分,我們先來看看上半部分

uint64_t length = CFDataGetLength(data); 
           

上面這句話就是可以拿到data資料中前8個位元組長度的資料(PS:uint64_t 相當于8個位元組)

const char *bytes = (char*)CFDataGetBytePtr(data); //轉成char類型的資料
uint32_t magic4 = *((uint32_t *)bytes);  //這句話再轉成位也就是的個位元組的資料
           

接下來就開始進行判斷了

咱們來就以PNG的判斷方法來分析

case YY_FOUR_CC(, 'P', 'N', 'G'): {  // PNG
            uint32_t tmp = *((uint32_t *)(bytes + ));
            if (tmp == YY_FOUR_CC('\r', '\n', , '\n')) {
                return YYImageTypePNG;
            }
        } break;
           

嗯……YY_FOUR_CC 是啥,咱們再點進去看看。

看了之後我們發現是一個宏定義,就是拼接這幾個十六進制位置的。

咱們接下來看,就來看

case YY_FOUR_CC(0x89, 'P', 'N', 'G')

前面我們也已經提到了,一般PNG格式的前4個位元組都是(89 50 4E 47)正好對應的就是 ‘.’ ‘P’ ‘N’ ‘G’;

其實我個人覺得前4個位元組其實已經可以完成判斷,但是YYWebImage的作者一定是個追求細節的人,他再判斷了4個位元組來完整的判斷出這張是一張PNG格式的圖檔

if (tmp == YY_FOUR_CC('\r', '\n', 0x1A, '\n'))

(PS:完整的判斷一張PNG格式的圖檔确實需要8個位元組)

判斷完成後就把圖檔的格式傳回出去,這就是YYWebImage判斷圖檔格式的基本原理啦。

SDWebImage判斷圖檔格式的方法

接下來咱們粗略的參考一下SDWebImage的圖檔格式,基本上應該差不多

咱們來看一下在NSData+ImageContentType.h 的頭檔案夾下有這麼一個方法

+ (SDImageFormat)sd_imageFormatForImageData:(nullable NSData *)data;

再來看看實作原理

+ (SDImageFormat)sd_imageFormatForImageData:(nullable NSData *)data {
    if (!data) {
        return SDImageFormatUndefined;
    }

    uint8_t c;
    [data getBytes:&c length:];
    switch (c) {
        case :
            return SDImageFormatJPEG;
        case :
            return SDImageFormatPNG;
        case :
            return SDImageFormatGIF;
        case :
        case :
            return SDImageFormatTIFF;
        case :
            // R as RIFF for WEBP
            if (data.length < ) {
                return SDImageFormatUndefined;
            }

            NSString *testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(, )] encoding:NSASCIIStringEncoding];
            if ([testString hasPrefix:@"RIFF"] && [testString hasSuffix:@"WEBP"]) {
                return SDImageFormatWebP;
            }
    }
    return SDImageFormatUndefined;
}
           

就是那麼的簡單粗暴。

uint8_t c; [data getBytes:&c length:1];

這直接就截取了前1個位元組長度的資料

再判斷每個位元組中的十六進制數所對應的格式

case 0x89: return SDImageFormatPNG;

結束語

條條大路通羅馬,能實作功能的就是好方法,差別在于快慢。

希望大家閱讀了本人的文章可以有所幫助

這是本人的初個部落格,希望大家不喜勿噴,如有錯誤或者想法可留言,我會吸取各個方面的建議,多多完善,還希望大家多多關照,互幫互助。謝謝大家抽出寶貴的時間來閱讀本人的文章。