談?wù)?iOS 中圖片的解壓縮

對于大多數(shù) iOS 應(yīng)用來說伶跷,圖片往往是最占用手機(jī)內(nèi)存的資源之一揉抵,同時也是不可或缺的組成部分品洛。將一張圖片從磁盤中加載出來,并最終顯示到屏幕上,中間其實經(jīng)過了一系列復(fù)雜的處理過程,其中就包括了對圖片的解壓縮蚁趁。

圖片加載的工作流

概括來說,從磁盤中加載一張圖片,并將它顯示到屏幕上番官,中間的主要工作流如下:

  1. 假設(shè)我們使用 +imageWithContentsOfFile: 方法從磁盤中加載一張圖片庐完,這個時候的圖片并沒有解壓縮;
  2. 然后將生成的 UIImage 賦值給 UIImageView 徘熔;
  3. 接著一個隱式的 CATransaction 捕獲到了 UIImageView 圖層樹的變化门躯;
  4. 在主線程的下一個 run loop 到來時,Core Animation 提交了這個隱式的 transaction 酷师,這個過程可能會對圖片進(jìn)行 copy 操作讶凉,而受圖片是否字節(jié)對齊等因素的影響,這個 copy 操作可能會涉及以下部分或全部步驟:
    1. 分配內(nèi)存緩沖區(qū)用于管理文件 IO 和解壓縮操作山孔;
    2. 將文件數(shù)據(jù)從磁盤讀到內(nèi)存中懂讯;
    3. 將壓縮的圖片數(shù)據(jù)解碼成未壓縮的位圖形式,這是一個非常耗時的 CPU 操作台颠;
    4. 最后 Core Animation 使用未壓縮的位圖數(shù)據(jù)渲染 UIImageView 的圖層褐望。

在上面的步驟中,我們提到了圖片的解壓縮是一個非常耗時的 CPU 操作蓉媳,并且它默認(rèn)是在主線程中執(zhí)行的譬挚。那么當(dāng)需要加載的圖片比較多時,就會對我們應(yīng)用的響應(yīng)性造成嚴(yán)重的影響酪呻,尤其是在快速滑動的列表上减宣,這個問題會表現(xiàn)得更加突出。

為什么需要解壓縮

既然圖片的解壓縮需要消耗大量的 CPU 時間玩荠,那么我們?yōu)槭裁催€要對圖片進(jìn)行解壓縮呢漆腌?是否可以不經(jīng)過解壓縮,而直接將圖片顯示到屏幕上呢阶冈?答案是否定的闷尿。要想弄明白這個問題,我們首先需要知道什么是位圖

A bitmap image (or sampled image) is an array of pixels (or samples). Each pixel represents a single point in the image. JPEG, TIFF, and PNG graphics files are examples of bitmap images.

其實女坑,位圖就是一個像素數(shù)組填具,數(shù)組中的每個像素就代表著圖片中的一個點。我們在應(yīng)用中經(jīng)常用到的 JPEG 和 PNG 圖片就是位圖匆骗。下面劳景,我們來看一個具體的例子,這是一張 PNG 圖片碉就,像素為 30?×?30 盟广,文件大小為 843B :

位圖

我們使用下面的代碼

UIImage *image = [UIImage imageNamed:@"check_green"];
CFDataRef rawData = CGDataProviderCopyData(CGImageGetDataProvider(image.CGImage));

就可以獲取到這個圖片的原始像素數(shù)據(jù),大小為 3600B :

00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
01020102 032c023c 0567048c 078d06bf 08a006d9 09b307f3 09b307f3 08a006d9 078d06bf
0567048c 032c023c 01020102 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 01060108 05570476 09ab07e9 09bb07ff 09bb07ff 09bb07ff 09bb07ff 09bb07ff
09bb07ff 09bb07ff 09bb07ff 09bb07ff 09bb07ff 09ab07e9 05570476 01060108 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 033d0353 08a607e2 09bb07ff 09bb07ff 09bb07ff 09bb07ff
...
09bb07ff 09bb07ff 09bb07ff 09bb07ff 08a607e2 033d0353 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 01060108 05570476 09ab07e9 09bb07ff 09bb07ff 09bb07ff 09bb07ff 09bb07ff
09bb07ff 09bb07ff 09bb07ff 09bb07ff 09bb07ff 09ab07e9 05570476 01060108 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 01020102 032c023c 0567048c
078d06bf 08a006d9 09b307f3 09b307f3 08a006d9 078d06bf 0567048c 032c023c 01020102
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000

也就是說瓮钥,這張文件大小為 843B 的 PNG 圖片解壓縮后的大小是 3600B 筋量,是原始文件大小的 4.27 倍烹吵。那么這個 3600B 是怎么得來的呢?與圖片的文件大小或者像素有什么必然的聯(lián)系嗎桨武?事實上肋拔,解壓縮后的圖片大小與原始文件大小之間沒有任何關(guān)系,而只與圖片的像素有關(guān):

解壓縮后的圖片大小 = 圖片的像素寬 30 * 圖片的像素高 30 * 每個像素所占的字節(jié)數(shù) 4

至于這個公式是怎么得來的呀酸,我們后面會有詳細(xì)的說明只损,現(xiàn)在只需要知道即可。

至此七咧,我們已經(jīng)知道了什么是位圖,并且直觀地看到了它的原始像素數(shù)據(jù)叮叹,那么它與我們經(jīng)常提到的圖片的二進(jìn)制數(shù)據(jù)有什么聯(lián)系嗎艾栋?是同一個東西嗎?事實上蛉顽,這二者是完全獨立的兩個東西蝗砾,它們之間沒有必然的聯(lián)系。為了加深理解携冤,我把這個圖片拖進(jìn) Sublime Text 2 中悼粮,得到了這個圖片的二進(jìn)制數(shù)據(jù),大小與原始文件大小一致曾棕,為 843B :

8950 4e47 0d0a 1a0a 0000 000d 4948 4452 0000 001e 0000 001e 0806 0000 003b 30ae a200
0000 0173 5247 4200 aece 1ce9 0000 0305 4944 4154 480d c557 4d68 1341 149e 3709 da4d
09c6 8a56 2385 9e14 f458 4fa2 d092 f4a6 28d8 2222 de04 3d09 a1d0 7a50 0954 8bad 2d05
4fde 3c89 482b 2ad6 8334 d183 e049 ef9e 4a41 48b0 42eb a549 6893 1ddf 9bcd b4d9 d9d9
4dd8 a43a b0d9 9d79 3fdf bc79 3ff3 02ac 8591 1559 3e97 9b3e 5b05 fb32 6330 c098 48a2
183d 340a b886 8ff8 1e15 fced 587a e26b 16b2 b643 f2ff 057f 1263 fd9f fbbb 7ed7 7edd
1142 8c09 268e 04f1 2a1a 3058 0380 b9c3 91de a7ab 43ab 15b5 aebf 7d81 ad65 eb0a 5a31
8f4f 9f2e d4da 1c7e e249 64ca c3e5 d726 7eae 2fa2 7510 cb75 3d62 cc5e 0c0f 4a5a 69c3
...
36ac b11e 7006 f71b 5386 a2b7 1e48 ad82 a26a 2880 95db 3f8b f525 b880 e0ed 7221 75f1
fa02 2cd4 1af7 1d0e 546a 98e5 d4ae 342a 337e 6b96 134f 1ba0 0c0b c83b a0f2 3593 7b5c
6ca9 b541 cb4f 254e df58 d958 8955 a0fc 2638 658c 2660 f986 b5f1 f4dd 63f2 5aec ce59
e3b6 b0a7 cdac ee55 145c c7dc 8f60 f53f e0a6 b436 e3c0 27b0 8ecf 5054 336a ccd0 e1d8
2335 1f78 323d 6141 09c3 c1aa 5f8b 4e37 0899 e6b0 ed72 4046 759e d262 5247 9d01 1689
a976 55fb c993 6ed5 7d10 8ff4 b162 fe6f cd1e ee4a d4bb c18e 594e 96ea 1da6 c762 6539
bdff 7943 afc0 c91f bdd1 a327 28fc 29f7 d47a b337 f192 0cc9 36fa 5497 73f9 5827 aa39
1599 4eff 69fb 0b0d 1f7a 96cd 3eb0 7800 0000 0049 454e 44ae 4260 82

事實上扣猫,不管是 JPEG 還是 PNG 圖片,都是一種壓縮的位圖圖形格式翘地。只不過 PNG 圖片是無損壓縮申尤,并且支持 alpha 通道,而 JPEG 圖片則是有損壓縮衙耕,可以指定 0-100% 的壓縮比昧穿。值得一提的是,在蘋果的 SDK 中專門提供了兩個函數(shù)用來生成 PNG 和 JPEG 圖片:

// return image as PNG. May return nil if image has no CGImageRef or invalid bitmap format
UIKIT_EXTERN NSData * __nullable UIImagePNGRepresentation(UIImage * __nonnull image);

// return image as JPEG. May return nil if image has no CGImageRef or invalid bitmap format. compression is 0(most)..1(least)                           
UIKIT_EXTERN NSData * __nullable UIImageJPEGRepresentation(UIImage * __nonnull image, CGFloat compressionQuality);

因此橙喘,在將磁盤中的圖片渲染到屏幕之前时鸵,必須先要得到圖片的原始像素數(shù)據(jù),才能執(zhí)行后續(xù)的繪制操作厅瞎,這就是為什么需要對圖片解壓縮的原因饰潜。

強(qiáng)制解壓縮的原理

既然圖片的解壓縮不可避免,而我們也不想讓它在主線程執(zhí)行磁奖,影響我們應(yīng)用的響應(yīng)性囊拜,那么是否有比較好的解決方案呢?答案是肯定的比搭。

我們前面已經(jīng)提到了冠跷,當(dāng)未解壓縮的圖片將要渲染到屏幕時南誊,系統(tǒng)會在主線程對圖片進(jìn)行解壓縮,而如果圖片已經(jīng)解壓縮了蜜托,系統(tǒng)就不會再對圖片進(jìn)行解壓縮抄囚。因此,也就有了業(yè)內(nèi)的解決方案橄务,在子線程提前對圖片進(jìn)行強(qiáng)制解壓縮幔托。

而強(qiáng)制解壓縮的原理就是對圖片進(jìn)行重新繪制,得到一張新的解壓縮后的位圖蜂挪。其中重挑,用到的最核心的函數(shù)是 CGBitmapContextCreate

/* 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);

顧名思義,這個函數(shù)用于創(chuàng)建一個位圖上下文棠涮,用來繪制一張寬 width 像素谬哀,高 height 像素的位圖。這個函數(shù)的注釋比較長严肪,參數(shù)也比較難理解史煎,但是先別著急,我們先來了解下相關(guān)的知識驳糯,然后再回過頭來理解這些參數(shù)篇梭,就會比較簡單了。

Pixel Format

我們前面已經(jīng)提到了酝枢,位圖其實就是一個像素數(shù)組恬偷,而像素格式則是用來描述每個像素的組成格式,它包括以下信息:

  • Bits per component :一個像素中每個獨立的顏色分量使用的 bit 數(shù)帘睦;
  • Bits per pixel :一個像素使用的總 bit 數(shù)喉磁;
  • Bytes per row :位圖中的每一行使用的字節(jié)數(shù)。

有一點需要注意的是官脓,對于位圖來說协怒,像素格式并不是隨意組合的,目前只支持以下有限的 17 種特定組合

從上圖可知卑笨,對于 iOS 來說孕暇,只支持 8 種像素格式。其中顏色空間為 Null 的 1 種赤兴,Gray 的 2 種妖滔,RGB 的 5 種,CMYK 的 0 種桶良。換句話說座舍,iOS 并不支持 CMYK 的顏色空間。另外陨帆,在表格的第 2 列中曲秉,除了像素格式外采蚀,還指定了 bitmap information constant ,我們在后面會詳細(xì)介紹承二。

Color and Color Spaces

在上面我們提到了顏色空間榆鼠,那么什么是顏色空間呢?它跟顏色有什么關(guān)系呢亥鸠?在 Quartz 中妆够,一個顏色是由一組值來表示的,比如 0, 0, 1 负蚊。而顏色空間則是用來說明如何解析這些值的神妹,離開了顏色空間,它們將變得毫無意義家妆。比如灾螃,下面的值都表示藍(lán)色:

image

如果不知道顏色空間,那么我們根本無法知道這些值所代表的顏色揩徊。比如 0, 0, 1 在 RGB 下代表藍(lán)色,而在 BGR 下則代表的是紅色嵌赠。在 RGB 和 BGR 兩種顏色空間下塑荒,綠色是相同的,而紅色和藍(lán)色則相互對調(diào)了姜挺。因此齿税,對于同一張圖片,使用 RGB 和 BGR 兩種顏色空間可能會得到兩種不一樣的效果:

color_profiles

是不是感覺非常有意思呢炊豪?

Color Spaces and Bitmap Layout

我們前面已經(jīng)知道了凌箕,像素格式是用來描述每個像素的組成格式的,比如每個像素使用的總 bit 數(shù)词渤。而要想確保 Quartz 能夠正確地解析這些 bit 所代表的含義牵舱,我們還需要提供位圖的布局信息 CGBitmapInfo

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);

它主要提供了三個方面的布局信息:

  • alpha 的信息;
  • 顏色分量是否為浮點數(shù)缺虐;
  • 像素格式的字節(jié)順序芜壁。

其中,alpha 的信息由枚舉值 CGImageAlphaInfo 來表示:

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 */
};

上面的注釋其實已經(jīng)比較清楚了高氮,它同樣也提供了三個方面的 alpha 信息:

  • 是否包含 alpha 慧妄;
  • 如果包含 alpha ,那么 alpha 信息所處的位置剪芍,在像素的最低有效位塞淹,比如 RGBA ,還是最高有效位罪裹,比如 ARGB 运挫;
  • 如果包含 alpha ,那么每個顏色分量是否已經(jīng)乘以 alpha 的值费彼,這種做法可以加速圖片的渲染時間滑臊,因為它避免了渲染時的額外乘法運算。比如箍铲,對于 RGB 顏色空間雇卷,用已經(jīng)乘以 alpha 的數(shù)據(jù)來渲染圖片,每個像素都可以避免 3 次乘法運算颠猴,紅色乘以 alpha 关划,綠色乘以 alpha 和藍(lán)色乘以 alpha 。

那么我們在解壓縮圖片的時候應(yīng)該使用哪個值呢翘瓮?根據(jù) Which CGImageAlphaInfo should we use 和官方文檔中對 UIGraphicsBeginImageContextWithOptions 函數(shù)的討論:

You use this function to configure the drawing environment for rendering into a bitmap. The format for the bitmap is a ARGB 32-bit integer pixel format using host-byte order. If the opaque parameter is YES, the alpha channel is ignored and the bitmap is treated as fully opaque (kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Host). Otherwise, each pixel uses a premultipled ARGB format (kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host).

我們可以知道贮折,當(dāng)圖片不包含 alpha 的時候使用 kCGImageAlphaNoneSkipFirst ,否則使用 kCGImageAlphaPremultipliedFirst 资盅。另外调榄,這里也提到了字節(jié)順序應(yīng)該使用 32 位的主機(jī)字節(jié)順序 kCGBitmapByteOrder32Host ,而這個值具體是什么呵扛,我們后面再討論每庆。

至于顏色分量是否為浮點數(shù),這個就比較簡單了今穿,直接邏輯或 kCGBitmapFloatComponents 就可以了缤灵。更詳細(xì)的內(nèi)容就不展開了,因為我們一般用不上這個值蓝晒。

接下來腮出,我們來簡單地了解下像素格式的字節(jié)順序,它是由枚舉值 CGImageByteOrderInfo 來表示的:

typedef CF_ENUM(uint32_t, CGImageByteOrderInfo) {
    kCGImageByteOrderMask     = 0x7000,
    kCGImageByteOrder16Little = (1 << 12),
    kCGImageByteOrder32Little = (2 << 12),
    kCGImageByteOrder16Big    = (3 << 12),
    kCGImageByteOrder32Big    = (4 << 12)
} CG_AVAILABLE_STARTING(__MAC_10_12, __IPHONE_10_0);

它主要提供了兩個方面的字節(jié)順序信息:

對于 iPhone 來說,采用的是小端模式洛二,但是為了保證應(yīng)用的向后兼容性慢逾,我們可以使用系統(tǒng)提供的宏,來避免 Hardcoding

#ifdef __BIG_ENDIAN__
    #define kCGBitmapByteOrder16Host kCGBitmapByteOrder16Big
    #define kCGBitmapByteOrder32Host kCGBitmapByteOrder32Big
#else /* Little endian. */
    #define kCGBitmapByteOrder16Host kCGBitmapByteOrder16Little
    #define kCGBitmapByteOrder32Host kCGBitmapByteOrder32Little
#endif

根據(jù)前面的討論灭红,我們知道字節(jié)順序的值應(yīng)該使用的是 32 位的主機(jī)字節(jié)順序 kCGBitmapByteOrder32Host 侣滩,這樣的話不管當(dāng)前設(shè)備采用的是小端模式還是大端模式,字節(jié)順序始終與其保持一致变擒。

下面君珠,我們來看一張圖,它非常形象地展示了在使用 16 或 32 位像素格式的 CMYK 和 RGB 顏色空間下娇斑,一個像素是如何被表示的:

[圖片上傳失敗...(image-711af-1543975607229)]

我們從圖中可以看出策添,在 32 位像素格式下材部,每個顏色分量使用 8 位;而在 16 位像素格式下唯竹,每個顏色分量則使用 5 位乐导。

好了,了解完這些相關(guān)知識后浸颓,我們再回過頭來看看 CGBitmapContextCreate 函數(shù)中每個參數(shù)所代表的具體含義:

  • data :如果不為 NULL 物臂,那么它應(yīng)該指向一塊大小至少為 bytesPerRow * height 字節(jié)的內(nèi)存;如果 為 NULL 产上,那么系統(tǒng)就會為我們自動分配和釋放所需的內(nèi)存棵磷,所以一般指定 NULL 即可;
  • widthheight :位圖的寬度和高度晋涣,分別賦值為圖片的像素寬度和像素高度即可仪媒;
  • bitsPerComponent :像素的每個顏色分量使用的 bit 數(shù),在 RGB 顏色空間下指定 8 即可谢鹊;
  • bytesPerRow :位圖的每一行使用的字節(jié)數(shù)算吩,大小至少為 width * bytes per pixel 字節(jié)。有意思的是佃扼,當(dāng)我們指定 0 時偎巢,系統(tǒng)不僅會為我們自動計算,而且還會進(jìn)行 cache line alignment 的優(yōu)化松嘶,更多信息可以查看 what is byte alignment (cache line alignment) for Core Animation? Why it matters?Why is my image’s Bytes per Row more than its Bytes per Pixel times its Width? ,親測可用挎扰;
  • space :就是我們前面提到的顏色空間翠订,一般使用 RGB 即可;
  • bitmapInfo :就是我們前面提到的位圖的布局信息遵倦。

到這里尽超,你已經(jīng)掌握了強(qiáng)制解壓縮圖片需要用到的最核心的函數(shù),點個贊梧躺。

開源庫的實現(xiàn)

接下來似谁,我們來看看在三個比較流行的開源庫 YYKitSDWebImageFLAnimatedImage 中掠哥,對圖片的強(qiáng)制解壓縮是如何實現(xiàn)的巩踏。

首先,我們來看看 YYKit 中的相關(guān)代碼续搀,用于解壓縮圖片的函數(shù) YYCGImageCreateDecodedCopy 存在于 YYImageCoder 類中塞琼,核心代碼如下:

CGImageRef YYCGImageCreateDecodedCopy(CGImageRef imageRef, BOOL decodeForDisplay) {
    ...

    if (decodeForDisplay) { // decode with redraw (may lose some precision)
        CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo(imageRef) & kCGBitmapAlphaInfoMask;

        BOOL hasAlpha = NO;
        if (alphaInfo == kCGImageAlphaPremultipliedLast ||
            alphaInfo == kCGImageAlphaPremultipliedFirst ||
            alphaInfo == kCGImageAlphaLast ||
            alphaInfo == kCGImageAlphaFirst) {
            hasAlpha = YES;
        }

        // BGRA8888 (premultiplied) or BGRX8888
        // same as UIGraphicsBeginImageContext() and -[UIView drawRect:]
        CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host;
        bitmapInfo |= hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst;

        CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, 0, YYCGColorSpaceGetDeviceRGB(), bitmapInfo);
        if (!context) return NULL;

        CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef); // decode
        CGImageRef newImage = CGBitmapContextCreateImage(context);
        CFRelease(context);

        return newImage;
    } else {
        ...
    }
}

它接受一個原始的位圖參數(shù) imageRef ,最終返回一個新的解壓縮后的位圖 newImage 禁舷,中間主要經(jīng)過了以下三個步驟:

  • 使用 CGBitmapContextCreate 函數(shù)創(chuàng)建一個位圖上下文彪杉;
  • 使用 CGContextDrawImage 函數(shù)將原始位圖繪制到上下文中毅往;
  • 使用 CGBitmapContextCreateImage 函數(shù)創(chuàng)建一張新的解壓縮后的位圖。

事實上派近,SDWebImage 和 FLAnimatedImage 中對圖片的解壓縮過程與上述完全一致攀唯,只是傳遞給 CGBitmapContextCreate 函數(shù)的部分參數(shù)存在細(xì)微的差別,如下表所示:

image

在上表中渴丸,用淺綠色背景標(biāo)記的參數(shù)即為我們在前面的分析中所推薦的參數(shù)侯嘀,用這些參數(shù)解壓縮后的圖片渲染的速度會更快。因此曙强,從理論上說 YYKit 中的解壓縮算法是三者之中最優(yōu)的残拐。

性能對比

口說無憑,因此我編寫了一個小的測試程序碟嘴,來簡單地對比一下這三個開源庫的解壓縮性能溪食,源碼可以在 GitHub 上找到。

采用的測試樣例分別為 5 張 PNG 圖片和 5 張 JPEG 圖片娜扇,像素依次為 128x96 错沃、256x192 、512x384 雀瓢、1024x768 和 2048x1536 枢析,它們其實都長一個樣:

128x96

首先,我們來了解下測試的原理刃麸,我們可以將從磁盤加載一張圖片到最終渲染到屏幕上的過程劃分為三個階段:

  • 初始化階段:從磁盤初始化圖片醒叁,生成一個未解壓縮的 UIImage 對象;
  • 解壓縮階段:分別使用 YYKit 泊业、SDWebImage 和 FLAnimatedImage 對第 1 步中得到的 UIImage 對象進(jìn)行解壓縮把沼,得到一個新的解壓縮后的 UIImage 對象;
  • 繪制階段:將第 2 步中得到的 UIImage 對象繪制到屏幕上吁伺。

這里我們以繪制階段的耗時為依據(jù)來評測解壓縮的性能饮睬,解壓縮的算法越優(yōu)秀,那么得到的圖片就越符合系統(tǒng)渲染時的需求篮奄,繪制的時間也就越短捆愁。為了讓測試的結(jié)果更準(zhǔn)確,我們對每張圖片都解壓縮 10 次窟却,然后取平均值昼丑。說明,本次使用的測試設(shè)備是 iPhone 5s 夸赫。

首先矾克,我們來看看解壓縮 PNG 圖片的測試結(jié)果:

image

相應(yīng)的柱狀圖如下:

image

從上圖可以看出,就我們采用的測試樣例來說,解壓縮 PNG 圖片的性能 SDWebImage 最好胁附,F(xiàn)LAnimatedImage 次之酒繁,YYKit 最差。這與我們前面的理論結(jié)果有一定的差距控妻,可能是測試樣例太少州袒,也可能這就是真實結(jié)果。另外弓候,需要說明的是贝润,我們這里使用的 PNG 圖片都是不帶 alpha 值瑟捣,因為 SDWebImage 不支持解壓縮帶 alpha 值的 PNG 圖片劫狠。

接著狰域,我們再來看看解壓縮 JPEG 圖片的測試結(jié)果:

image

相應(yīng)的柱狀圖如下:

image

這次 YYKit 終于翻盤了,解壓縮 JPEG 圖片的性能最好依鸥,SDWebImage 和 FLAnimatedImage 并列第二亥至。

總結(jié)

其實,要理解 iOS 中圖片的解壓縮并不難贱迟,重點是要理解位圖的概念姐扮。而圖片解壓縮的過程其實就是將圖片的二進(jìn)制數(shù)據(jù)轉(zhuǎn)換成像素數(shù)據(jù)的過程。了解這些知識衣吠,將有助于我們更好地處理圖片茶敏,管理好它們所占用的內(nèi)存。

參考鏈接

《處理圖片的小技巧》
https://www.cocoanetics.com/2011/10/avoiding-image-decompression-sickness/
https://developer.apple.com/library/content/documentation/GraphicsImaging/Conceptual/drawingwithquartz2d/Introduction/Introduction.html
https://github.com/path/FastImageCache
http://stackoverflow.com/questions/23790837/what-is-byte-alignment-cache-line-alignment-for-core-animation-why-it-matters

原文作者:雷純鋒
原文鏈接:http://blog.leichunfeng.com/blog/2017/02/20/talking-about-the-decompression-of-the-image-in-ios/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末缚俏,一起剝皮案震驚了整個濱河市惊搏,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌忧换,老刑警劉巖恬惯,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異包雀,居然都是意外死亡宿崭,警方通過查閱死者的電腦和手機(jī)亲铡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進(jìn)店門才写,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人奖蔓,你說我怎么就攤上這事赞草。” “怎么了吆鹤?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵厨疙,是天一觀的道長。 經(jīng)常有香客問我疑务,道長沾凄,這世上最難降的妖魔是什么梗醇? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮撒蟀,結(jié)果婚禮上叙谨,老公的妹妹穿的比我還像新娘。我一直安慰自己保屯,他們只是感情好手负,可當(dāng)我...
    茶點故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著姑尺,像睡著了一般竟终。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上切蟋,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天统捶,我揣著相機(jī)與錄音,去河邊找鬼敦姻。 笑死瘾境,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的镰惦。 我是一名探鬼主播迷守,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼旺入!你這毒婦竟也來了兑凿?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤茵瘾,失蹤者是張志新(化名)和其女友劉穎礼华,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體拗秘,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡圣絮,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了雕旨。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片扮匠。...
    茶點故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖凡涩,靈堂內(nèi)的尸體忽然破棺而出棒搜,到底是詐尸還是另有隱情,我是刑警寧澤活箕,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布力麸,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏克蚂。R本人自食惡果不足惜闺鲸,卻給世界環(huán)境...
    茶點故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望埃叭。 院中可真熱鬧翠拣,春花似錦、人聲如沸游盲。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽益缎。三九已至谜慌,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間莺奔,已是汗流浹背欣范。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留令哟,地道東北人恼琼。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓,卻偏偏與公主長得像屏富,于是被迫代替她去往敵國和親晴竞。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,037評論 2 355

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