前言
客戶端研發(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ù)格式的大概模樣,如下圖:
圖中我們只需要關(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 圖的文件頭格式圖:
代碼實(shí)現(xiàn):
-
方式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:直接下載,在網(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é)
- 在數(shù)據(jù)的提取過程中,需要注意大小端問題導(dǎo)致的數(shù)據(jù)解析出來不對(相關(guān)知識);
- 即使通過這種方式進(jìn)行優(yōu)化,獲取圖片大小問題仍然因?yàn)樾枰l(fā)送網(wǎng)絡(luò)請求而變的速度不夠穩(wěn)定,所以真正的解決方案,還是需要服務(wù)端配合添加上圖片數(shù)據(jù)寬高的記錄;
- 實(shí)際應(yīng)用中需要和緩存配合來達(dá)到最佳效果。