通過 URL 獲取圖片寬高優(yōu)化

一張小圖.png

前言

客戶端研發(fā)時,有時會有這樣的需求,需要根據(jù)圖片鏈接地址獲取圖片的寬高來進(jìn)行界面排版。

一般比較正規(guī)的做法,是服務(wù)端在返回數(shù)據(jù)時將圖片的信息屬性一起帶回來,這也符合輕客戶端設(shè)計規(guī)范。但是現(xiàn)實(shí)不是理想,有時就是會出現(xiàn)服務(wù)端沒有返回,你卻要知道圖片寬高,所以本文,針對通過 URL 來獲取圖片寬高進(jìn)行簡單的介紹。

傳統(tǒng)獲取圖片寬高方案:

最為常見也是最慢的一種方案,通過 URL 下載圖片,得到圖片數(shù)據(jù)后獲取圖片寬高。

這種方式 iOS 下有很多實(shí)現(xiàn)方案,可以使用三方工具進(jìn)行圖片下載,也可以直接自己寫,該方法思想就是通過下載整個圖片然后得到完整的圖片數(shù)據(jù)信息,解析數(shù)據(jù),再從中得到圖片的寬高。 比如本文使用 CGImageSource 來通過 URL 圖片信息,從而得到圖片的寬高。代碼如下:

// 獲取圖像屬性
CFDictionaryRef imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSourceRef, 0, NULL);

返回的 imageProperties 就已經(jīng)從整個圖片數(shù)據(jù)中獲取到需要的信息了,我們只需要從中通過 KEY 來取出需要的值。

// 獲取寬高
CFNumberRef widthNumberRef = CFDictionaryGetValue(imageProperties, kCGImagePropertyPixelWidth);
CFNumberRef heightNumberRef = CFDictionaryGetValue(imageProperties, kCGImagePropertyPixelHeight);

改進(jìn)方案:

在傳統(tǒng)方案里,獲取圖片的寬高的時候,下載了整個圖片數(shù)據(jù),但這里我們的需求只是獲取圖片的寬高信息,此時還不需要全部的圖片信息,下載完整的圖片數(shù)據(jù),導(dǎo)致需要獲取的數(shù)據(jù)量更大,從而增加的寬高獲取到的時間,減少了我們的流暢性。

如果我們通過 URL 只下載我們需要的圖片中的某些信息的話(比如寬高信息),就能減少此次請求的傳輸?shù)臄?shù)據(jù)量,從而加快信息的獲取。那如何實(shí)現(xiàn)呢?

在圖片數(shù)據(jù)中,不管什么格式,在表示該圖片的數(shù)據(jù)中都有一段數(shù)據(jù)塊表示著這個圖片的描述信息,其中就包含著該圖片的寬高大小。所以知道這點(diǎn),我們只要通過 URL 請求獲取該部分段信息,就可以從中解析出我們需要的圖片寬高。

讓我們先看下 PNG 圖片的數(shù)據(jù)格式的大概模樣,如下圖:

PNG數(shù)據(jù)頭數(shù)據(jù)圖

圖中我們只需要關(guān)注 PNG Signature 與標(biāo)紅的 WIDTH HEIGHT 段,PNG Signature標(biāo)志著該圖片是一張 PNG 圖,知道它是 PNG 圖數(shù)據(jù)后,如果圖片數(shù)據(jù)的開頭與這張圖中一致,則代表該圖片是一張 PNG 圖。

在數(shù)據(jù)的固定位置處,即 WIDTH HEIGHT 所在的字節(jié)位置里存放的就是該圖片的寬高信息,所以我們只需要從該處取出所存數(shù)據(jù)就知道圖片寬高了。

同理針對其他格式的圖片也是一樣的,只是他們中數(shù)據(jù)的段格式以及位置有些不同,但都存在著這樣一個數(shù)據(jù)段表示著圖片的描述信息。(這里并不對所有的圖片格式進(jìn)行介紹,這里了解資料)

下面是 GIF 圖的文件頭格式圖:

GIF頭格式

代碼實(shí)現(xiàn):

  1. 方式1:通過設(shè)置 HTTP 請求頭 Range 字段來獲取數(shù)據(jù)的某位置段數(shù)據(jù);

    比如,此時有一張 PNG 圖的鏈接地址,想要知道其寬高,代碼如下:

     NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
     [request setValue:@"bytes=16-23" forHTTPHeaderField:@"Range"];
     NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:nil
                                                      error:nil];
     CGSize size = CGSizeZero;
     if (data.length >= 8) {
         int w = 0, h = 0;
         [data getBytes:&w range:NSMakeRange(0, 4)];
         [data getBytes:&h range:NSMakeRange(4, 4)];
         w = CFSwapInt32BigToHost(w);
         h = CFSwapInt32BigToHost(h);
         size = CGSizeMake(w, h);
     }
    

    16-23字節(jié)位置處,前4字節(jié)代表著寬,后4字節(jié)代表著高,由此我們就完成了圖片的寬高獲取,相對于傳統(tǒng)方式,不管圖片真實(shí)大小多大,我們只下載了僅僅 8 字節(jié)的數(shù)據(jù),無疑加快了速度和節(jié)省了流量,其他格式圖代碼可見文件。

  2. 方式2:直接下載,在網(wǎng)絡(luò)回調(diào)中解析數(shù)據(jù),得到足夠數(shù)據(jù)后,解析出寬高,提前停止請求。
    在第一種方式中,雖然速度很快但存在一個問題,下載前必須先知道圖片寬高數(shù)據(jù)存儲位置,對于 PNG 和 GIF 圖片來說是沒有問題,但在 JPG 格式圖時,由于其數(shù)據(jù)段并不是在文件的頭部,也不再固定的位置,可能在中間的任何一段地方,所以通過提前指定請求頭 的 Range 范圍是無法有效獲取到信息的,此時我們只能通過一邊下載圖片數(shù)據(jù),一邊在解析得到的數(shù)據(jù),如果檢測到了圖片的描述信息段,則開始解析,解析成功后提前結(jié)束網(wǎng)絡(luò)請求,這樣在速度和流量方面相對于傳統(tǒng)的依然是有一定的提升。下圖為 JPG 圖數(shù)據(jù)格式:

    image

    其中 FFCO段為描述段信息開頭,我們在代碼中通過While 來在一個個數(shù)據(jù)段中尋找該描述段,找到了它就找到了寬高。
    代碼如下:


    - (CGSize)fetchHWFromJPGData:(NSData *)data {
    CGSize size = CGSizeZero;
    // FF D8 FF E0 (XX XX 這兩字節(jié)為長度) ('JF' 'TF' 轉(zhuǎn)為ascll碼值)
    UInt8 word0 = 0x0, word1 = 0x0, word2 = 0x0, word3 = 0x0;
    [data getBytes:&word0 range:NSMakeRange(0, 1)];
    [data getBytes:&word1 range:NSMakeRange(1, 1)];
    [data getBytes:&word2 range:NSMakeRange(2, 1)];
    [data getBytes:&word3 range:NSMakeRange(3, 1)];
    if (word0 == 0xFF && word1 == 0xD8 && word2 == 0xFF && word3 == 0xE0) {
        UInt8 c0 = 0, c1 = 0, c2 = 0, c3 = 0;
        [data getBytes:&c0 range:NSMakeRange(6, 1)];
        [data getBytes:&c1 range:NSMakeRange(7, 1)];
        [data getBytes:&c2 range:NSMakeRange(8, 1)];
        [data getBytes:&c3 range:NSMakeRange(9, 1)];
        if (c0 == 'J' && c1 == 'F' && c2 == 'I' && c3 == 'F') {
            UInt16 block_length = 0;
            [data getBytes:&block_length range:NSMakeRange(4, 2)];
            block_length = XCSSwapWebIntToInt16(block_length);
            int i = 4;
            do {
                i += block_length;
                if (i > data.length) {
                    break;
                }
                UInt8 aW = 0x0;
                [data getBytes:&aW range:NSMakeRange(i, 1)];
                if (aW != 0xFF) {
                    break;
                }
                
                UInt8 ca = 0x0;
                [data getBytes:&ca range:NSMakeRange(i+1, 1)];
                if (ca >= 0xC0 && ca<= 0xC3) {
                    /**
                     圖片信息段 FF CO (xx xx 該段長度) XX(拋棄字節(jié),不用) (HH HH 高度) (WW WW 寬度) ...后面是其他信息。
                     這里寬高段和 png gif 等都不一樣,jpg 是高在前
                     */
                    UInt16 w = 0, h = 0;
                    [data getBytes:&h range:NSMakeRange(i + 5, 2)];
                    [data getBytes:&w range:NSMakeRange(i + 7, 2)];
                    w = XCSSwapWebIntToInt16(w);
                    h = XCSSwapWebIntToInt16(h);
                    size = CGSizeMake(w, h);
                    break;
                }else {
                    i += 2;
                    [data getBytes:&block_length range:NSMakeRange(i, 2)];
                    block_length = XCSSwapWebIntToInt16(block_length);
                }
            } while (i < data.length);
        }
        
    }
    return size;
}

更多詳情代碼,可見DEMO

最后結(jié)果代碼

XCSImagePrefetcher 通過傳入 URL 來獲取圖片的寬高。

@interface XCSImagePrefetcher : NSObject

@property (nonatomic, strong, readonly) NSMutableData *downloadData;
@property (nonatomic, strong, readonly) NSURL *imageUrl;

- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithUrl:(NSURL *)url NS_DESIGNATED_INITIALIZER;
- (CGSize)fetchImageSize;

@end

使用方式如下:


    XCSImagePrefetcher *fetcher = [[XCSImagePrefetcher alloc] initWithUrl:[NSURL URLWithString:JPG_IMG_URL]];
    NSLog(@"%@", NSStringFromCGSize([fetcher fetchImageSize]));

更多測試代碼,可見DEMO。

總結(jié)

  1. 在數(shù)據(jù)的提取過程中,需要注意大小端問題導(dǎo)致的數(shù)據(jù)解析出來不對(相關(guān)知識);
  2. 即使通過這種方式進(jìn)行優(yōu)化,獲取圖片大小問題仍然因?yàn)樾枰l(fā)送網(wǎng)絡(luò)請求而變的速度不夠穩(wěn)定,所以真正的解決方案,還是需要服務(wù)端配合添加上圖片數(shù)據(jù)寬高的記錄;
  3. 實(shí)際應(yīng)用中需要和緩存配合來達(dá)到最佳效果。

相關(guān)資料

  1. 理解字節(jié)序大小端
  2. 各種圖頭信息格式匯總
  3. HTTP 頭字段了解
  4. 國外大神文章關(guān)于SWIFT版獲取圖片,本文主要實(shí)現(xiàn)也是參考它的, 感謝
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 第一部分 HTML&CSS整理答案 1. 什么是HTML5? 答:HTML5是最新的HTML標(biāo)準(zhǔn)。 注意:講述HT...
    kismetajun閱讀 27,627評論 1 45
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對...
    cosWriter閱讀 11,135評論 1 32
  • ¥開啟¥ 【iAPP實(shí)現(xiàn)進(jìn)入界面執(zhí)行逐一顯】 〖2017-08-25 15:22:14〗 《//首先開一個線程,因...
    小菜c閱讀 6,508評論 0 17
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,198評論 4 61
  • 寫在最后 普拉克西特利斯是公元前4世紀(jì)的雕塑家,傳說芙麗涅是他的情人,若說芙麗涅除了美貌之外還有什么才能,那大概就...
    藝萃閱讀 2,240評論 0 6