SDWebImage探究(十九) —— 圖像的解碼 (一)

版本記錄

版本號(hào) 時(shí)間
V1.0 2018.02.25

前言

我們做APP括袒,文字和圖片是絕對(duì)不可缺少的元素厦滤,特別是圖片一般存儲(chǔ)在圖床里面援岩,一般公司可以委托第三方保存,NB的公司也可以自己存儲(chǔ)圖片掏导,ios有很多圖片加載的第三方框架享怀,其中最優(yōu)秀的莫過(guò)于SDWebImage,它幾乎可以滿(mǎn)足你所有的需求趟咆,用了好幾年這個(gè)框架添瓷,今天想總結(jié)一下。感興趣的可以看其他幾篇值纱。
1. SDWebImage探究(一)
2. SDWebImage探究(二)
3. SDWebImage探究(三)
4. SDWebImage探究(四)
5. SDWebImage探究(五)
6. SDWebImage探究(六) —— 圖片類(lèi)型判斷深入研究
7. SDWebImage探究(七) —— 深入研究圖片下載流程(一)之有關(guān)option的位移枚舉的說(shuō)明
8. SDWebImage探究(八) —— 深入研究圖片下載流程(二)之開(kāi)始下載并返回下載結(jié)果的總的方法
9. SDWebImage探究(九) —— 深入研究圖片下載流程(三)之下載之前的緩存查詢(xún)操作
10. SDWebImage探究(十) —— 深入研究圖片下載流程(四)之查詢(xún)緩存后的block回調(diào)處理
11. SDWebImage探究(十一) —— 深入研究圖片下載流程(五)之SDWebImageDownloadToken和操作對(duì)象的生成和返回
12. SDWebImage探究(十二) —— 深入研究圖片下載流程(六)之下載器到具體下載操作的代理分發(fā)實(shí)現(xiàn)
13. SDWebImage探究(十三) —— 深入研究圖片下載流程(七)之NSURLSession中幾個(gè)代理的基本用法和關(guān)系
14. SDWebImage探究(十四) —— 深入研究圖片下載流程(八)之下載完成代理方法的調(diào)用
15. SDWebImage探究(十五) —— 深入研究圖片下載流程(九)之身份驗(yàn)證質(zhì)詢(xún)代理方法調(diào)用
16. SDWebImage探究(十六) —— 深入研究圖片下載流程(十)之緩存相關(guān)代理方法調(diào)用
17. SDWebImage探究(十七) —— 深入研究圖片下載流程(十一)之收到響應(yīng)代理方法調(diào)用
18. SDWebImage探究(十八) —— 深入研究圖片下載流程(十二)之收到圖像數(shù)據(jù)代理方法調(diào)用

圖像為什么需要解碼

一般下載或者從磁盤(pán)獲取的圖片是PNG或者JPG鳞贷,這是經(jīng)過(guò)編碼壓縮后的圖片數(shù)據(jù),不是位圖虐唠,要把它們渲染到屏幕前就需要進(jìn)行解碼轉(zhuǎn)成位圖數(shù)據(jù)搀愧,而這個(gè)解碼操作比較耗時(shí)。你也可以這么理解疆偿,圖片在遠(yuǎn)端存儲(chǔ)一定都是編碼后存儲(chǔ)的咱筛,這樣體積小,一個(gè)圖像可以看做是一個(gè)圖像文件杆故,里面包含了文件頭迅箩,文件體和文件尾,圖像的數(shù)據(jù)就包含在文件體中处铛,而我們的解碼就是運(yùn)用算法將文件體中的圖像數(shù)據(jù)轉(zhuǎn)化為位圖數(shù)據(jù)沙热,方便渲染和展示叉钥。

iOS默認(rèn)是在主線(xiàn)程解碼罢缸,所以SDWebImage將這個(gè)過(guò)程放到子線(xiàn)程了篙贸。

同時(shí)因?yàn)槲粓D體積很大,所以磁盤(pán)緩存不會(huì)直接緩存位圖數(shù)據(jù)枫疆,而是編碼壓縮后的PNG或JPG數(shù)據(jù)爵川。


圖像是否可以解碼的判斷

這里有幾種情況是不進(jìn)行解碼的。

  • 動(dòng)圖息楔,判斷image.images != nil寝贡,滿(mǎn)足這個(gè)條件的就是動(dòng)圖,就不解碼值依。
  • 有alpha信息的圖片不解碼圃泡。

1. 動(dòng)圖

這里首先要糾正一個(gè)概念,動(dòng)態(tài)圖片也不僅僅限于gif愿险,png格式也有動(dòng)態(tài)的颇蜡,只是常見(jiàn)的是GIF格式的比較多而已。

這里動(dòng)圖為什么不解碼呢辆亏?

這個(gè)我還沒(méi)找到正確的答案风秤,猜測(cè)可能是動(dòng)圖解碼后的數(shù)據(jù)占用內(nèi)存太大,所以就不對(duì)動(dòng)圖進(jìn)行解碼了扮叨。關(guān)于GIF動(dòng)圖的支持在FLAnimatedImageView分類(lèi)中缤弦。

2. 有alpha信息的圖片

(a) 有alpha信息的圖片的判斷

首先我們需要判斷的就是什么圖片包含alpha信息,蘋(píng)果提供了支持彻磁。

/* Return the alpha info of `image'. */

CG_EXTERN CGImageAlphaInfo CGImageGetAlphaInfo(CGImageRef cg_nullable image)
    CG_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0);
CGImageRef imageRef = image.CGImage;
    
CGImageAlphaInfo alpha = CGImageGetAlphaInfo(imageRef);
BOOL anyAlpha = (alpha == kCGImageAlphaFirst ||
                     alpha == kCGImageAlphaLast ||
                     alpha == kCGImageAlphaPremultipliedFirst ||
                     alpha == kCGImageAlphaPremultipliedLast);

這里需要說(shuō)明的就是CGImageAlphaInfo碍沐,這個(gè)是什么鬼?

typedef CF_ENUM(uint32_t, CGImageAlphaInfo) {
    kCGImageAlphaNone,               /* For example, RGB. */
    kCGImageAlphaPremultipliedLast,  /* For example, premultiplied RGBA */
    kCGImageAlphaPremultipliedFirst, /* For example, premultiplied ARGB */
    kCGImageAlphaLast,               /* For example, non-premultiplied RGBA */
    kCGImageAlphaFirst,              /* For example, non-premultiplied ARGB */
    kCGImageAlphaNoneSkipLast,       /* For example, RBGX. */
    kCGImageAlphaNoneSkipFirst,      /* For example, XRGB. */
    kCGImageAlphaOnly                /* No color data, alpha data only */
};

這是CoreGraphic框架中CGImage.h中的一個(gè)枚舉衷蜓。


圖像的解碼實(shí)現(xiàn)

SDWebImage中有關(guān)于圖像解碼的部分累提,我們先看一下實(shí)現(xiàn)方式,然后進(jìn)行詳細(xì)的解析恍箭。

+ (nullable UIImage *)decodedImageWithImage:(nullable UIImage *)image {
    if (![UIImage shouldDecodeImage:image]) {
        return image;
    }
    
    // autorelease the bitmap context and all vars to help system to free memory when there are memory warning.
    // on iOS7, do not forget to call [[SDImageCache sharedImageCache] clearMemory];
    @autoreleasepool{
        
        CGImageRef imageRef = image.CGImage;
        CGColorSpaceRef colorspaceRef = [UIImage colorSpaceForImageRef:imageRef];
        
        size_t width = CGImageGetWidth(imageRef);
        size_t height = CGImageGetHeight(imageRef);
        size_t bytesPerRow = kBytesPerPixel * width;

        // kCGImageAlphaNone is not supported in CGBitmapContextCreate.
        // Since the original image here has no alpha info, use kCGImageAlphaNoneSkipLast
        // to create bitmap graphics contexts without alpha info.
        CGContextRef context = CGBitmapContextCreate(NULL,
                                                     width,
                                                     height,
                                                     kBitsPerComponent,
                                                     bytesPerRow,
                                                     colorspaceRef,
                                                     kCGBitmapByteOrderDefault|kCGImageAlphaNoneSkipLast);
        if (context == NULL) {
            return image;
        }
        
        // Draw the image into the context and retrieve the new bitmap image without alpha
        CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef);
        CGImageRef imageRefWithoutAlpha = CGBitmapContextCreateImage(context);
        UIImage *imageWithoutAlpha = [UIImage imageWithCGImage:imageRefWithoutAlpha
                                                         scale:image.scale
                                                   orientation:image.imageOrientation];
        
        CGContextRelease(context);
        CGImageRelease(imageRefWithoutAlpha);
        
        return imageWithoutAlpha;
    }
}
+ (BOOL)shouldDecodeImage:(nullable UIImage *)image {
    // Prevent "CGBitmapContextCreateImage: invalid context 0x0" error
    if (image == nil) {
        return NO;
    }

    // do not decode animated images
    if (image.images != nil) {
        return NO;
    }
    
    CGImageRef imageRef = image.CGImage;
    
    CGImageAlphaInfo alpha = CGImageGetAlphaInfo(imageRef);
    BOOL anyAlpha = (alpha == kCGImageAlphaFirst ||
                     alpha == kCGImageAlphaLast ||
                     alpha == kCGImageAlphaPremultipliedFirst ||
                     alpha == kCGImageAlphaPremultipliedLast);
    // do not decode images with alpha
    if (anyAlpha) {
        return NO;
    }
    
    return YES;
}
+ (CGColorSpaceRef)colorSpaceForImageRef:(CGImageRef)imageRef {
    // current
    CGColorSpaceModel imageColorSpaceModel = CGColorSpaceGetModel(CGImageGetColorSpace(imageRef));
    CGColorSpaceRef colorspaceRef = CGImageGetColorSpace(imageRef);
    
    BOOL unsupportedColorSpace = (imageColorSpaceModel == kCGColorSpaceModelUnknown ||
                                  imageColorSpaceModel == kCGColorSpaceModelMonochrome ||
                                  imageColorSpaceModel == kCGColorSpaceModelCMYK ||
                                  imageColorSpaceModel == kCGColorSpaceModelIndexed);
    if (unsupportedColorSpace) {
        colorspaceRef = CGColorSpaceCreateDeviceRGB();
        CFAutorelease(colorspaceRef);
    }
    return colorspaceRef;
}

這里做的就是首先判斷什么樣的圖像可以解碼刻恭,是一個(gè)類(lèi)方法,如果不能就直接返回傳入的image參數(shù)扯夭,如果可以解碼就對(duì)圖像進(jìn)行解碼后返回鳍贾。

這里有幾個(gè)點(diǎn)需要說(shuō)明:

(a) CGBitmapContextCreate上下文的創(chuàng)建

kCGImageAlphaNoneCGBitmapContextCreate中不受支持。由于此處的原始圖像沒(méi)有alpha信息交洗,因此請(qǐng)使用kCGImageAlphaNoneSkipLast創(chuàng)建不帶alpha信息的位圖圖形上下文骑科。

這里我們看一下,這個(gè)上下文創(chuàng)建的API

/* Create a bitmap context. The context draws into a bitmap which is `width'
   pixels wide and `height' pixels high. The number of components for each
   pixel is specified by `space', which may also specify a destination color
   profile. The number of bits for each component of a pixel is specified by
   `bitsPerComponent'. The number of bytes per pixel is equal to
   `(bitsPerComponent * number of components + 7)/8'. Each row of the bitmap
   consists of `bytesPerRow' bytes, which must be at least `width * bytes
   per pixel' bytes; in addition, `bytesPerRow' must be an integer multiple
   of the number of bytes per pixel. `data', if non-NULL, points to a block
   of memory at least `bytesPerRow * height' bytes. If `data' is NULL, the
   data for context is allocated automatically and freed when the context is
   deallocated. `bitmapInfo' specifies whether the bitmap should contain an
   alpha channel and how it's to be generated, along with whether the
   components are floating-point or integer. */

CG_EXTERN CGContextRef __nullable CGBitmapContextCreate(void * __nullable data,
    size_t width, size_t height, size_t bitsPerComponent, size_t bytesPerRow,
    CGColorSpaceRef cg_nullable space, uint32_t bitmapInfo)
    CG_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0);

創(chuàng)建一個(gè)位圖上下文构拳。 上下文繪制成寬度為width像素和height像素為高的位圖咆爽。 每個(gè)像素的組件數(shù)量由space指定梁棠,它也可以指定目標(biāo)顏色配置文件。 像素的每個(gè)分量的位數(shù)由bitsPerComponent指定斗埂。 每個(gè)像素的字節(jié)數(shù)等于(bitsPerComponent *components+7)/ 8符糊。 位圖的每一行都由bytesPerRow字節(jié)組成,它們必須至少為每個(gè)像素的字節(jié)數(shù) * 寬度width呛凶。另外男娄,bytesPerRow必須是每個(gè)像素字節(jié)數(shù)的整數(shù)倍。 data漾稀,如果非NULL模闲,則至少指向bytesPerRow * height字節(jié)的內(nèi)存塊。 如果data為NULL崭捍,上下文數(shù)據(jù)將自動(dòng)分配尸折,并在釋放上下文時(shí)釋放。 bitmapInfo指定位圖是否應(yīng)該包含一個(gè)alpha通道以及如何生成殷蛇,以及組件是浮點(diǎn)還是整數(shù)实夹。

(b) 位圖信息CGBitmapInfo

最后一個(gè)參數(shù),關(guān)于CGBitmapInfo的一個(gè)枚舉晾咪。

typedef CF_OPTIONS(uint32_t, CGBitmapInfo) {
    kCGBitmapAlphaInfoMask = 0x1F,

    kCGBitmapFloatInfoMask = 0xF00,
    kCGBitmapFloatComponents = (1 << 8),

    kCGBitmapByteOrderMask     = kCGImageByteOrderMask,
    kCGBitmapByteOrderDefault  = (0 << 12),
    kCGBitmapByteOrder16Little = kCGImageByteOrder16Little,
    kCGBitmapByteOrder32Little = kCGImageByteOrder32Little,
    kCGBitmapByteOrder16Big    = kCGImageByteOrder16Big,
    kCGBitmapByteOrder32Big    = kCGImageByteOrder32Big
} CG_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0);

這里存放的就是位圖組成部分的信息收擦。

  • kCGBitmapAlphaInfoMask
    • Aplha通道信息遮罩圈纺。用這個(gè)值來(lái)提取alpha信息嗜傅。這個(gè)值明確了位圖是否包含了alpha通道和alpha通道是如何生成的。
  • kCGBitmapFloatComponents
    • 位圖數(shù)據(jù)都是浮點(diǎn)值跌前。
  • kCGBitmapByteOrderMask
    • 像素格式的字節(jié)順序
  • kCGBitmapByteOrderDefault
    • 默認(rèn)的字節(jié)順序
  • kCGBitmapByteOrder16Little
    • 16位小端格式
  • kCGBitmapByteOrder32Little
    • 32位小端格式
  • kCGBitmapByteOrder16Big
    • 16位大端格式
  • kCGBitmapByteOrder32Big
    • 32位大端格式

(c) 生成圖像顏色空間CGColorSpaceRef

// current
CGColorSpaceModel imageColorSpaceModel = CGColorSpaceGetModel(CGImageGetColorSpace(imageRef));
CGColorSpaceRef colorspaceRef = CGImageGetColorSpace(imageRef);

BOOL unsupportedColorSpace = (imageColorSpaceModel == kCGColorSpaceModelUnknown ||
                              imageColorSpaceModel == kCGColorSpaceModelMonochrome ||
                              imageColorSpaceModel == kCGColorSpaceModelCMYK ||
                              imageColorSpaceModel == kCGColorSpaceModelIndexed);
if (unsupportedColorSpace) {
    colorspaceRef = CGColorSpaceCreateDeviceRGB();
    CFAutorelease(colorspaceRef);
}
return colorspaceRef;

這里昼蛀,CGColorSpaceRef表示需要使用的色彩標(biāo)準(zhǔn)(為創(chuàng)建CGColor做準(zhǔn)備)例如RBG:CGColorSpaceCreateDeviceRGB宴猾。

Quartz支持由顏色管理系統(tǒng)使用的與設(shè)備無(wú)關(guān)的顏色空間的標(biāo)準(zhǔn)顏色空間,并且還支持通用叼旋,索引和模式顏色空間仇哆。 設(shè)備無(wú)關(guān)的顏色空間以在設(shè)備之間可移植的方式表示顏色。 它們用于將顏色數(shù)據(jù)從一個(gè)設(shè)備的本地顏色空間到另一個(gè)設(shè)備的本地顏色空間的交換夫植。 在不同設(shè)備上顯示時(shí)讹剔,設(shè)備無(wú)關(guān)顏色空間中的顏色顯示相同,只要設(shè)備的功能允許详民。 因此延欠,與設(shè)備無(wú)關(guān)的顏色空間是表示顏色的最佳選擇。

具有精確顏色要求的應(yīng)用程序應(yīng)始終使用與設(shè)備無(wú)關(guān)的顏色空間沈跨。 常見(jiàn)的設(shè)備無(wú)關(guān)顏色空間是通用顏色空間由捎。 通用顏色空間使操作系統(tǒng)為您的應(yīng)用程序提供最佳的顏色空間。 繪圖到顯示看起來(lái)像打印相同的內(nèi)容到打印機(jī)饿凛。

重點(diǎn):iOS不支持設(shè)備無(wú)關(guān)或通用顏色空間狞玛。 iOS應(yīng)用程序必須使用設(shè)備顏色空間软驰。

下面看一下CGColorSpaceModel,顏色空間模型心肪。

/* The model of a color space. */

typedef CF_ENUM (int32_t,  CGColorSpaceModel) {
    kCGColorSpaceModelUnknown = -1,
    kCGColorSpaceModelMonochrome,
    kCGColorSpaceModelRGB,
    kCGColorSpaceModelCMYK,
    kCGColorSpaceModelLab,
    kCGColorSpaceModelDeviceN,
    kCGColorSpaceModelIndexed,
    kCGColorSpaceModelPattern
};

參考文章

1. SDWebImageDecoder引發(fā)的思考
2. CGBitmapInfo
3. 【CoreGraphics】CGColorSpace - 色彩空間
4. Color and Color Spaces

后記

本篇文章主要解析了圖像解碼相關(guān)锭亏,包括為什么要進(jìn)行圖像解碼、是否可以解碼的判斷以及解碼的代碼實(shí)現(xiàn)等幾個(gè)問(wèn)題蒙畴。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末贰镣,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子膳凝,更是在濱河造成了極大的恐慌,老刑警劉巖恭陡,帶你破解...
    沈念sama閱讀 223,207評(píng)論 6 521
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蹬音,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡休玩,警方通過(guò)查閱死者的電腦和手機(jī)著淆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,455評(píng)論 3 400
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)拴疤,“玉大人永部,你說(shuō)我怎么就攤上這事∧欧” “怎么了苔埋?”我有些...
    開(kāi)封第一講書(shū)人閱讀 170,031評(píng)論 0 366
  • 文/不壞的土叔 我叫張陵,是天一觀(guān)的道長(zhǎng)蜒犯。 經(jīng)常有香客問(wèn)我组橄,道長(zhǎng),這世上最難降的妖魔是什么罚随? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 60,334評(píng)論 1 300
  • 正文 為了忘掉前任玉工,我火速辦了婚禮,結(jié)果婚禮上淘菩,老公的妹妹穿的比我還像新娘遵班。我一直安慰自己,他們只是感情好潮改,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,322評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布狭郑。 她就那樣靜靜地躺著,像睡著了一般进陡。 火紅的嫁衣襯著肌膚如雪愿阐。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,895評(píng)論 1 314
  • 那天趾疚,我揣著相機(jī)與錄音缨历,去河邊找鬼以蕴。 笑死,一個(gè)胖子當(dāng)著我的面吹牛辛孵,可吹牛的內(nèi)容都是我干的丛肮。 我是一名探鬼主播,決...
    沈念sama閱讀 41,300評(píng)論 3 424
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼魄缚,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼宝与!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起冶匹,我...
    開(kāi)封第一講書(shū)人閱讀 40,264評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤习劫,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后嚼隘,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體诽里,經(jīng)...
    沈念sama閱讀 46,784評(píng)論 1 321
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,870評(píng)論 3 343
  • 正文 我和宋清朗相戀三年飞蛹,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了谤狡。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,989評(píng)論 1 354
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡卧檐,死狀恐怖墓懂,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情霉囚,我是刑警寧澤捕仔,帶...
    沈念sama閱讀 36,649評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站佛嬉,受9級(jí)特大地震影響逻澳,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜暖呕,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,331評(píng)論 3 336
  • 文/蒙蒙 一斜做、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧湾揽,春花似錦瓤逼、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,814評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至戚揭,卻和暖如春诱告,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背民晒。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,940評(píng)論 1 275
  • 我被黑心中介騙來(lái)泰國(guó)打工精居, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留锄禽,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,452評(píng)論 3 379
  • 正文 我出身青樓靴姿,卻偏偏與公主長(zhǎng)得像沃但,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子佛吓,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,995評(píng)論 2 361

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