導(dǎo)語(yǔ)
目前關(guān)于網(wǎng)絡(luò)下載圖片的框架,除了十分熱門的SDWebImage之外還有很多,比如PINRemoteImage、FlyImage等許許多多優(yōu)秀的框架。這篇文章主要講的YYWebImage當(dāng)然也是其中的一員。寫這篇文章的主要目的是希望能和大家一起探討一下網(wǎng)絡(luò)下載圖片是如何來(lái)判定格式的原理
YYWebImage的作者是ibireme傲茄,Git網(wǎng)址:https://github.com/ibireme/YYWebImage
此網(wǎng)站上十分詳細(xì)的寫了如何安裝以及使用YYWebImage
SDWebImagegit網(wǎng)址:https://github.com/rs/SDWebImage
在閑暇之余,研究一下大神寫的代碼,分析一下他們是如何實(shí)現(xiàn)判斷圖片格式的代碼拙吉。
首先,為大家普及一下圖片的格式(PS:不知道的可以看看揪荣,知道的就跳過(guò)哈)
圖片常見的格式有:GIF 筷黔、PNG、JPEG仗颈、BMP佛舱、TIFF 等。
其實(shí)每一個(gè)圖片格式都有對(duì)應(yīng)的十六進(jìn)制數(shù)據(jù)(PS:當(dāng)然十六進(jìn)制也是從二進(jìn)制轉(zhuǎn)換過(guò)來(lái)的)挨决,也可以說(shuō)就是這些十六進(jìn)制數(shù)據(jù)組成了一張圖片请祖,然后再通過(guò)計(jì)算機(jī)內(nèi)部的渲染等一系列算法從而顯示了一張圖片,而往往前面的4~8個(gè)字節(jié)往往都代表了這張圖片的格式
如:
89 50 4E 47 0D 0A 1A 0A 00 00 …………
前面的4個(gè)字節(jié)(89 50 4E 47)的ASCII 對(duì)應(yīng)的就是 ‘.’ ‘P’ ‘N’ ‘G’;
再如:
47 49 46 38 39 61 64 00 …….
前面的4個(gè)字節(jié)的ASCII 對(duì)應(yīng)的就是’G’ ‘I’ ‘F’ ‘8’
(PS:為啥上傳不了圖片脖祈,要不然截個(gè)圖超省事…….)
接下來(lái)咱們就一起看看YYWebImage
在YYWebImage的 YYWebImageOperation.m 中找到了這么一段代碼
YYImageType imageType = YYImageDetectType((__bridge CFDataRef)self.data);
switch (imageType) {
case YYImageTypeJPEG:
case YYImageTypeGIF:
case YYImageTypePNG:
case YYImageTypeWebP: { // save to disk cacheif(!hasAnimation) {if(imageType == YYImageTypeGIF ||
imageType == YYImageTypeWebP) {
self.data = nil; // clear the data, re-encodefordisk cache
}
}
}break;
default: {
self.data = nil; // clear the data, re-encodefordisk cache
}break;
}
這是一段已經(jīng)要返回出圖片格式了肆捕,咱們點(diǎn)進(jìn)YYImageDetectType這個(gè)方法看看,里面是如何實(shí)現(xiàn)的
YYImageType YYImageDetectType(CFDataRef data) {if(!data)returnYYImageTypeUnknown;
uint64_t length = CFDataGetLength(data);if(length <16)returnYYImageTypeUnknown;
const char *bytes = (char *)CFDataGetBytePtr(data);
uint32_t magic4 = *((uint32_t *)bytes);
switch (magic4) {
case YY_FOUR_CC(0x4D,0x4D,0x00,0x2A): { // big endian TIFFreturnYYImageTypeTIFF;
}break;
case YY_FOUR_CC(0x49,0x49,0x2A,0x00): { // little endian TIFFreturnYYImageTypeTIFF;
}break;
case YY_FOUR_CC(0x00,0x00,0x01,0x00): { // ICOreturnYYImageTypeICO;
}break;
......
正如我們預(yù)料的那樣盖高,YYWebImage同樣也是用每個(gè)圖片的開頭前4個(gè)或前8個(gè)字節(jié)的數(shù)據(jù)來(lái)判斷的
接下來(lái)進(jìn)一步分析一下:
這個(gè)方法很長(zhǎng)我就截取了一部分慎陵,我們先來(lái)看看上半部分
uint64_t length = CFDataGetLength(data);
上面這句話就是可以拿到data數(shù)據(jù)中前8個(gè)字節(jié)長(zhǎng)度的數(shù)據(jù)(PS:uint64_t 相當(dāng)于8個(gè)字節(jié))
const char *bytes = (char*)CFDataGetBytePtr(data); //轉(zhuǎn)成char類型的數(shù)據(jù)uint32_t magic4 = *((uint32_t *)bytes);? //這句話再轉(zhuǎn)成32位也就是的4個(gè)字節(jié)的數(shù)據(jù)
接下來(lái)就開始進(jìn)行判斷了
咱們來(lái)就以PNG的判斷方法來(lái)分析
case YY_FOUR_CC(0x89,'P','N','G'): {? // PNG
uint32_t tmp = *((uint32_t *)(bytes +4));if(tmp == YY_FOUR_CC('\r','\n',0x1A,'\n')) {returnYYImageTypePNG;
}
}break;
嗯……YY_FOUR_CC 是啥,咱們?cè)冱c(diǎn)進(jìn)去看看喻奥。
#define YY_FOUR_CC(c1,c2,c3,c4) ((uint32_t)(((c4) << 24) | ((c3) << 16) | ((c2) << 8) | (c1)))
看了之后我們發(fā)現(xiàn)是一個(gè)宏定義席纽,就是拼接這幾個(gè)十六進(jìn)制位置的。
咱們接下來(lái)看撞蚕,就來(lái)看case YY_FOUR_CC(0x89, 'P', 'N', 'G')
前面我們也已經(jīng)提到了润梯,一般PNG格式的前4個(gè)字節(jié)都是(89 50 4E 47)正好對(duì)應(yīng)的就是 ‘.’ ‘P’ ‘N’ ‘G’;
其實(shí)我個(gè)人覺(jué)得前4個(gè)字節(jié)其實(shí)已經(jīng)可以完成判斷,但是YYWebImage的作者一定是個(gè)追求細(xì)節(jié)的人诈豌,他再判斷了4個(gè)字節(jié)來(lái)完整的判斷出這張是一張PNG格式的圖片if (tmp == YY_FOUR_CC('\r', '\n', 0x1A, '\n'))(PS:完整的判斷一張PNG格式的圖片確實(shí)需要8個(gè)字節(jié))
判斷完成后就把圖片的格式返回出去仆救,這就是YYWebImage判斷圖片格式的基本原理啦。
接下來(lái)咱們粗略的參考一下SDWebImage的圖片格式矫渔,基本上應(yīng)該差不多
咱們來(lái)看一下在NSData+ImageContentType.h 的頭文件夾下有這么一個(gè)方法
+ (SDImageFormat)sd_imageFormatForImageData:(nullable NSData *)data;
再來(lái)看看實(shí)現(xiàn)原理
+ (SDImageFormat)sd_imageFormatForImageData:(nullable NSData *)data {if(!data) {returnSDImageFormatUndefined;? ? }? ? uint8_t c;? ? [data getBytes:&c length:1];? ? switch (c) {? ? ? ? case0xFF:returnSDImageFormatJPEG;? ? ? ? case0x89:returnSDImageFormatPNG;? ? ? ? case0x47:returnSDImageFormatGIF;? ? ? ? case0x49:? ? ? ? case0x4D:returnSDImageFormatTIFF;? ? ? ? case0x52:? ? ? ? ? ? // RasRIFFforWEBPif(data.length <12) {returnSDImageFormatUndefined;? ? ? ? ? ? }? ? ? ? ? ? NSString *testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(0,12)] encoding:NSASCIIStringEncoding];if([testString hasPrefix:@"RIFF"] && [testString hasSuffix:@"WEBP"]) {returnSDImageFormatWebP;? ? ? ? ? ? }? ? }returnSDImageFormatUndefined;}
就是那么的簡(jiǎn)單粗暴彤蔽。
uint8_t c;
[data getBytes:&c length:1];
這直接就截取了前1個(gè)字節(jié)長(zhǎng)度的數(shù)據(jù)
再判斷每個(gè)字節(jié)中的十六進(jìn)制數(shù)所對(duì)應(yīng)的格式
case 0x89: return SDImageFormatPNG;