iOS圖片加載淺析

要講圖片格式還先得從圖像的基本數(shù)據(jù)結(jié)構(gòu)說(shuō)起。在計(jì)算機(jī)中, 圖像是由一個(gè)個(gè)像素點(diǎn)組成,像素點(diǎn)就是顏色點(diǎn),而顏色最簡(jiǎn)單的方式就是用RGB或RGBA表示, 如圖所示。


圖片格式

如果有A通道就表明這個(gè)圖像可以有透明效果。


有透明度的圖片

圖片格式介紹

JPEG 是目前最常見(jiàn)的圖片格式,它誕生于 1992 年,是一個(gè)很古老的格式。它只支持有損壓縮,其壓縮算法可以精確控制壓縮比,以圖像質(zhì)量換得存儲(chǔ)空間。由于它太過(guò)常見(jiàn),以至于許多移動(dòng)設(shè)備的 CPU都支持針對(duì)它的硬編碼與硬解碼。jpg是最常見(jiàn)的圖像格式,圖像占用的存儲(chǔ)較小,但是犧牲了圖像的質(zhì)量。

PNG 誕生在 1995 年,比 JPEG 晚幾年。它本身的設(shè)計(jì)目的是替代 GIF 格式,所以它與 GIF 有更多相似的地方。PNG 只支持無(wú)損壓縮,所以它的壓縮比是有上限的。相對(duì)于 JPEG 和 GIF 來(lái)說(shuō),它最大的優(yōu)勢(shì)在于支持完整的透明通道。png圖像的大小是jpg圖像大小的數(shù)倍,png為可移植網(wǎng)絡(luò)圖形格式,也是一種位圖文件存儲(chǔ)格式,可以進(jìn)行無(wú)損壓縮。

GIF 誕生于 1987 年,隨著初代互聯(lián)網(wǎng)流行開(kāi)來(lái)。它有很多缺點(diǎn),比如通常情況下只支持 256 種顏色、透明通道只有 1 bit、文件壓縮比不高。它唯一的優(yōu)勢(shì)就是支持多幀動(dòng)畫(huà),憑借這個(gè)特性,它得以從 Windows 1.0 時(shí)代流行至今,而且仍然大受歡迎。

在上面這些圖片格式誕生后,也有不少公司或團(tuán)體嘗試對(duì)他們進(jìn)行改進(jìn),或者創(chuàng)造其他更加優(yōu)秀的圖片格式,比如 JPEG 小組的 JPEG 2000、微軟的 JPEG-XR、Google 的 WebP、個(gè)人開(kāi)發(fā)者發(fā)布的 BPG、FLIF 等。它們相對(duì)于老牌的那幾個(gè)圖片格式來(lái)說(shuō)有了很大的進(jìn)步,但出于各種各樣的原因,只有少數(shù)幾個(gè)格式能夠流行開(kāi)來(lái)。下面三種就是目前實(shí)力比較強(qiáng)的新興格式了:

APNG 是 Mozilla 在 2008 年發(fā)布的一種圖片格式,旨在替換掉畫(huà)質(zhì)低劣的 GIF 動(dòng)畫(huà)。它實(shí)際上只是相當(dāng)于 PNG 格式的一個(gè)擴(kuò)展,所以 Mozilla 一直想把它合并到 PNG 標(biāo)準(zhǔn)里面去。然而 PNG 開(kāi)發(fā)組并沒(méi)有接受 APNG 這個(gè)擴(kuò)展,而是一直在推進(jìn)它自己的 MNG 動(dòng)圖格式。MNG 格式過(guò)于復(fù)雜以至于并沒(méi)有什么系統(tǒng)或?yàn)g覽器支持,而 APNG 格式由于簡(jiǎn)單容易實(shí)現(xiàn),目前已經(jīng)漸漸流行開(kāi)來(lái)。Mozilla 自己的 Firefox 首先支持了 APNG,隨后蘋(píng)果的 Safari 也開(kāi)始有了支持, Chrome 目前也已經(jīng)嘗試開(kāi)始支持 ,可以說(shuō)未來(lái)前景很好。

WebP 是 Google 在 2010 年發(fā)布的圖片格式,希望以更高的壓縮比替代 JPEG。它用 VP8 視頻幀內(nèi)編碼作為其算法基礎(chǔ),取得了不錯(cuò)的壓縮效果。它支持有損和無(wú)損壓縮、支持完整的透明通道、也支持多幀動(dòng)畫(huà),并且沒(méi)有版權(quán)問(wèn)題,是一種非常理想的圖片格式。借由 Google 在網(wǎng)絡(luò)世界的影響力,WebP 在幾年的時(shí)間內(nèi)已經(jīng)得到了廣泛的應(yīng)用。看看你手機(jī)里的 App:微博、微信、QQ、淘寶、網(wǎng)易新聞等等,每個(gè) App 里都有 WebP 的身影。Facebook 則更進(jìn)一步,用 WebP 來(lái)顯示聊天界面的貼紙動(dòng)畫(huà)。

iOS 底層是用 ImageIO.framework 實(shí)現(xiàn)的圖片編解碼。目前 iOS 原生支持的格式有:JPEG、JPEG2000、PNG、GIF、BMP、ICO、TIFF、PICT,自 iOS 8.0 起,ImageIO 又加入了 APNG、SVG、RAW 格式的支持。在上層,開(kāi)發(fā)者可以直接調(diào)用 ImageIO 對(duì)上面這些圖片格式進(jìn)行編碼和解碼。對(duì)于動(dòng)圖來(lái)說(shuō),開(kāi)發(fā)者可以解碼動(dòng)畫(huà) GIF 和 APNG、可以編碼動(dòng)畫(huà) GIF。

兩個(gè)平臺(tái)在導(dǎo)入第三方編解碼庫(kù)時(shí),都多少對(duì)他們進(jìn)行了一些修改,比如 Android 對(duì) libjpeg 等進(jìn)行的調(diào)整以更好的控制內(nèi)存,iOS 對(duì) libpng 進(jìn)行了修改以支持 APNG,并增加了多線程編解碼的特性。除此之外,iOS 專(zhuān)門(mén)針對(duì) JPEG 的編解碼開(kāi)發(fā)了 AppleJPEG.framework,實(shí)現(xiàn)了性能更高的硬編碼和硬解碼,只有當(dāng)硬編碼解碼失敗時(shí),libjpeg 才會(huì)被用到。

JPEG

目前比較知名的 JPEG 庫(kù)有以下三個(gè)

libjpeg:開(kāi)發(fā)時(shí)間最早,使用最廣泛的 JPEG 庫(kù)。由于 JPEG 標(biāo)準(zhǔn)過(guò)于復(fù)雜和模糊,并沒(méi)有其他人去實(shí)現(xiàn),所以這個(gè)庫(kù)是 JPEG 的事實(shí)標(biāo)準(zhǔn)。

libjpeg-turbo:一個(gè)致力于提升編解碼速度的 JPEG 庫(kù)。它基于 libjpeg 進(jìn)行了改造,用 SIMD 指令集 (MMX、SSE2、NEON) 重寫(xiě)了部分代碼,官網(wǎng)稱(chēng)相對(duì)于 libjpeg 有 2 到 4 倍的性能提升。

MozJPEG: 一個(gè)致力于提升壓縮比的 JPEG 庫(kù)。它是 Mozilla 在 2014 年發(fā)布的基于 libjpeg-turbo 進(jìn)行改造的庫(kù),相對(duì)于 libjpeg 有 5% ~ 15%? 的壓縮比提升,但相應(yīng)的其編碼速度也慢了很多。

除了上面這三個(gè)庫(kù),蘋(píng)果自己也開(kāi)發(fā)了一個(gè) AppleJPEG,但并沒(méi)有開(kāi)源。其調(diào)用了芯片提供的 DSP 硬編碼和硬解碼的功能。雖然它不如上面這三個(gè)庫(kù)功能完善,但其性能非常高。在我的測(cè)試中,其編解碼速度通常是 libjpeg-turbo 的 1~2 倍。可惜的是,目前開(kāi)發(fā)者并不能直接訪問(wèn)這個(gè)庫(kù)。

PNG

相對(duì)于 JPEG 來(lái)說(shuō),PNG 標(biāo)準(zhǔn)更為清晰和簡(jiǎn)單,因此有很多公司或個(gè)人都有自己的 PNG 編碼解碼實(shí)現(xiàn)。但目前使用最廣的還是 PNG 官方發(fā)布的 libpng 庫(kù)。iOS 和 Android 底層都是調(diào)用這個(gè)庫(kù)實(shí)現(xiàn)的 PNG 編解碼。

圖片加載工作流程

概括來(lái)說(shuō),從磁盤(pán)中加載一張圖片,并將它顯示到屏幕上,中間的主要工作流如下:


?假設(shè)我們使用 **+imageWithContentsOfFile:** 方法從磁盤(pán)中加載一張圖片,這個(gè)時(shí)候的圖片并沒(méi)有解壓縮;

?然后將生成的 UIImage 賦值給 UIImageView ;

?接著一個(gè)隱式的 CATransaction 捕獲到了 UIImageView 圖層樹(shù)的變化;

?在主線程的下一個(gè) run loop 到來(lái)時(shí),Core Animation 提交了這個(gè)隱式的 transaction ,這個(gè)過(guò)程可能會(huì)對(duì)圖片進(jìn)行 copy 操作,而受圖片是否字節(jié)對(duì)齊等因素的影響,這個(gè) copy 操作可能會(huì)涉及以下部分或全部步驟:

? ? 分配內(nèi)存緩沖區(qū)用于管理文件 IO 和解壓縮操作;

? ? 將文件數(shù)據(jù)從磁盤(pán)讀到內(nèi)存中;

? ? 將壓縮的圖片數(shù)據(jù)解碼成未壓縮的位圖形式,這是一個(gè)非常耗時(shí)的 CPU 操作;

? ? 最后 Core Animation 使用未壓縮的位圖數(shù)據(jù)渲染 UIImageView 的圖層。

為什么需要解壓縮

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

其實(shí),位圖就是一個(gè)像素?cái)?shù)組,數(shù)組中的每個(gè)像素就代表著圖片中的一個(gè)點(diǎn)。我們?cè)趹?yīng)用中經(jīng)常用到的 JPEG 和 PNG 圖片就是位圖。下面,我們來(lái)看一個(gè)具體的例子以240 X 240像素為例,這是一張 PNG 圖片


test.png

文件大小為37KB,37,888 字節(jié)

也就是說(shuō),這張文件大小為 37,888 B 的 PNG 圖片解壓縮后的大小是 230,400B ,是原始文件大小的 6.08 倍。那么這個(gè) 230,400B 是怎么得來(lái)的呢?與圖片的文件大小或者像素有什么必然的聯(lián)系嗎?事實(shí)上,解壓縮后的圖片大小與原始文件大小之間沒(méi)有任何關(guān)系,而只與圖片的像素有關(guān):

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

事實(shí)上,不管是 JPEG 還是 PNG 圖片,都是一種壓縮的位圖圖形格式。只不過(guò) PNG 圖片是無(wú)損壓縮,并且支持 alpha 通道,而 JPEG 圖片則是有損壓縮,可以指定 0-100% 的壓縮比。值得一提的是,在蘋(píng)果的 SDK 中專(zhuān)門(mén)提供了兩個(gè)函數(shù)用來(lái)生成 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);? ?

蘋(píng)果對(duì) JPEG 有硬編碼和硬解碼,保存成 JPEG 會(huì)大大縮減編碼解碼時(shí)間,也能減小文件體積。所以如果圖片不包含透明像素時(shí),UIImageJPEGRepresentation(0.9) 是最佳的圖片保存方式,其次是 UIImagePNGRepresentation()。 因此,在將磁盤(pán)中的圖片渲染到屏幕之前,必須先要得到圖片的原始像素?cái)?shù)據(jù),才能執(zhí)行后續(xù)的繪制操作,這就是為什么需要對(duì)圖片解壓縮的原因。

Webp

WebP 的優(yōu)勢(shì)體現(xiàn)在它具有更優(yōu)的圖像數(shù)據(jù)壓縮算法,能帶來(lái)更小的圖片體積,而且擁有肉眼識(shí)別無(wú)差異的圖像質(zhì)量;同時(shí)具備了無(wú)損和有損的壓縮模式、Alpha 透明以及動(dòng)畫(huà)的特性,在 JPEG 和 PNG 上的轉(zhuǎn)化效果都相當(dāng)優(yōu)秀、穩(wěn)定和統(tǒng)一。

WebP圖片相比于JPG,擁有:

1、更小的文件尺寸;

2、更高的質(zhì)量——與其他相同大小不同格式的壓縮圖像比較。

3、目標(biāo)圖像的質(zhì)量和文件大小之間存在明顯的折中關(guān)系。在很多情況下,可以很大程度降低圖像的大小,而用戶(hù)卻幾乎不會(huì)注意到其中的差別。

根據(jù)Google的測(cè)試,目前WebP與JPG相比較,編碼速度慢10倍,解碼速度慢1.5倍。

在編碼方面,一般來(lái)說(shuō),我們可以在圖片上傳時(shí)生成一份WebP圖片或者在第一次訪問(wèn)JPG圖片時(shí)生成WebP圖片,對(duì)用戶(hù)體驗(yàn)的影響基本忽略不計(jì),主要問(wèn)題在于1.5倍的解碼速度是否會(huì)影響用戶(hù)體驗(yàn)

測(cè)試數(shù)據(jù)折線圖如下:


折線圖

從折線圖可以看到,WebP雖然會(huì)增加額外的解碼時(shí)間,但由于減少了文件體積,縮短了加載的時(shí)間,頁(yè)面的渲染速度加快了。同時(shí),隨著圖片數(shù)量的增多,WebP頁(yè)面加載的速度相對(duì)JPG頁(yè)面增快了。所以,使用WebP基本沒(méi)有技術(shù)阻礙,還能帶來(lái)性能提升以及帶寬節(jié)省。

JavaScript檢測(cè)是否支持WebP代碼如下:(出自Google官方文檔)

function check_webp_feature(feature, callback) {

? ? var kTestImages = {

? ? ? ? lossy: "UklGRiIAAABXRUJQVlA4IBYAAAAwAQCdASoBAAEADsD+JaQAA3AAAAAA",

? ? ? ? lossless: "UklGRhoAAABXRUJQVlA4TA0AAAAvAAAAEAcQERGIiP4HAA==",

? ? ? ? alpha: "UklGRkoAAABXRUJQVlA4WAoAAAAQAAAAAAAAAAAAQUxQSAwAAAARBxAR/Q9ERP8DAABWUDggGAAAABQBAJ0BKgEAAQAAAP4AAA3AAP7mtQAAAA==",

? ? ? ? animation: "UklGRlIAAABXRUJQVlA4WAoAAAASAAAAAAAAAAAAQU5JTQYAAAD/////AABBTk1GJgAAAAAAAAAAAAAAAAAAAGQAAABWUDhMDQAAAC8AAAAQBxAREYiI/gcA"

? ? };

? ? var img = new Image();

? ? img.onload = function () {

? ? ? ? var result = (img.width > 0) && (img.height > 0);

? ? ? ? callback(feature, result);

? ? };

? ? img.onerror = function () {

? ? ? ? callback(feature, false);

? ? };

? ? img.src = "data:image/webp;base64," + kTestImages[feature];

? ? }

在瀏覽器向服務(wù)器發(fā)起請(qǐng)求時(shí),對(duì)于支持WebP圖片的瀏覽器,會(huì)在請(qǐng)求頭Accept中帶上image/webp的信息,服務(wù)器便能識(shí)別到瀏覽器是否支持WebP,在服務(wù)器中處理圖片。

webp在iOS設(shè)備上

當(dāng)前的iOS不支持webp,不知道以后會(huì)不會(huì)支持,所以從網(wǎng)絡(luò)上拿到一個(gè)webp格式的圖片后,并不能直接顯示出來(lái),需要把data數(shù)據(jù)轉(zhuǎn)化為jpg或者png來(lái)顯示。

使用SDWebImage中帶的WebP

> 在podfile中加入

> pod 'SDWebImage/WebP

可以在SDWebImage中加入U(xiǎn)IImage的WebP類(lèi)別,同時(shí)會(huì)引入libwebp。在使用SD加載圖片時(shí),會(huì)判定圖片的格式,如果為webp,調(diào)用Google的libwebp庫(kù)進(jìn)行解析。

調(diào)用過(guò)程如下:

?sd_setImageWithURL ->

URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error ->

sd_imageWithData->

sd_imageWithWebPData

處理成功后會(huì)返回一個(gè)UIImage。

在WebView中使用Webp格式圖片

如果有一些web頁(yè)中使用了webp格式的圖片,僅用上述方法是不行的,我推薦的做法是寫(xiě)一個(gè)自定義的URLSession protocol, 繼承自NSURLProtocol, 然后注冊(cè)。

?- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(nullable NSError *)error

{

? if (error == nil)

? {

? ? ? ? if ([task.currentRequest.URL.absoluteString hasSuffix:@"webp"])

? ? ? ? {

? ? ? ? ? ? NSLog(@"webp will changed:%@",task.currentRequest.URL);

? ? ? ? ? ? UIImage *imgData = [UIImage sd_imageWithData:self.imageData];

? ? ? ? ? ? NSData *transData = UIImageJPEGRepresentation(imgData, 0.8f);

? ? ? ? ? ? self.beginAppendData = NO;

? ? ? ? ? ? self.imageData = nil;

? ? ? ? ? ? [self.client URLProtocol:self didLoadData:transData];

? ? ? ? }

? ? ? ? [self.client URLProtocolDidFinishLoading:self];

? ? }

? ? else if ( [[error domain] isEqual:NSURLErrorDomain] && ([error code] == NSURLErrorCancelled) )

? ? {

? ? ? ? [self.client URLProtocol:self didFailWithError:error];

? ? }

? ? else

? ? {

? ? ? ? [[self client] URLProtocol:self didFailWithError:error];

? ? }

Demo地址

參考鏈接:

http://blog.leichunfeng.com/blog/2017/02/20/talking-about-the-decompression-of-the-image-in-ios/

https://blog.csdn.net/HerbenLam/article/details/53432004

https://blog.ibireme.com/2015/11/02/ios_image_tips/

https://zh.wikipedia.org/zh-hans/WebP

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

  • 移動(dòng)端圖片格式調(diào)研 圖片通常是移動(dòng)端流量耗費(fèi)最多的部分,并且占據(jù)著重要的視覺(jué)空間。合理的圖片格式選用和優(yōu)化可以為你...
    AngeloD閱讀 1,250評(píng)論 0 5
  • 圖片通常是移動(dòng)端流量耗費(fèi)最多的部分,并且占據(jù)著重要的視覺(jué)空間。合理的圖片格式選用和優(yōu)化可以為你節(jié)省帶寬、提升視覺(jué)效...
    傻傻小蘿卜閱讀 784評(píng)論 1 9
  • 今天早上五點(diǎn)還沒(méi)到就醒了,真應(yīng)了那句話(huà)叫醒你的不是鬧鐘是夢(mèng)想,我從沒(méi)像加入葆嬰這般的渴望成功,感受到成功,我知道我...
    Hi_張閱讀 284評(píng)論 3 2