iOS圖片的渲染過程與性能優(yōu)化

圖片的存儲方式

圖片和其他所有資源一樣循头,在內(nèi)存中本質(zhì)上都是0和1的二進(jìn)制數(shù)據(jù)尖飞,用戶無法接觸到這些二進(jìn)制數(shù)據(jù)筛圆,他們看到的都是經(jīng)過某種二進(jìn)制編碼之后的圖片篙顺。這種將圖片以某種規(guī)則進(jìn)行二進(jìn)制編碼的方式面褐,就是圖片的格式拌禾。
常見的格式有:
JPEG
只支持有損壓縮,其壓縮算法可以精確控制壓縮比展哭,以圖像質(zhì)量換得存儲空間湃窍。
PNG
只支持無損壓縮,最大的優(yōu)勢在于支持完整的透明通道匪傍。
GIF
支持多幀動畫您市。
WebP
支持有損和無損壓縮、支持完整的透明通道役衡、無損壓縮后的 webp 比 png 少了45%的體積茵休,相同質(zhì)量的 webp 和 jpg,前者也能節(jié)省一半的流量。同時 webp 還支持動圖榕莺,可謂圖片壓縮格式的集大成者俐芯。缺點:WebP格式圖像的編碼時間很長,是JPEG的8倍钉鸯;瀏覽器和移動端支持還不是很完善泼各。

這里簡單介紹下有損壓縮和無損壓縮:
有損壓縮:相較于顏色,人眼對光線亮度信息更為敏感亏拉,基于此扣蜻,通過合并圖片中的顏色信息,保留亮度信息及塘,可以在盡量不影響圖片觀感的前提下減少存儲體積莽使。顧名思義,這樣壓縮后的圖片將會永久損失一些細(xì)節(jié)笙僚。最典型的有損壓縮格式是 jpg芳肌。

無損壓縮:和有損壓縮不同,無損壓縮不會損失圖片細(xì)節(jié)肋层。它降低圖片體積的方式是通過索引亿笤,對圖片中不同的顏色特征建立索引表,減少了重復(fù)的顏色數(shù)據(jù)栋猖,從而達(dá)到壓縮的效果净薛。常見的無損壓縮格式是 png,gif蒲拉。

如何判斷圖片的格式
由原始的二進(jìn)制數(shù)據(jù)肃拜,根據(jù)不同壓縮方式的編碼特征,就可以拿到,以SDWebImage為例

+ (SDImageFormat)sd_imageFormatForImageData:(nullable NSData *)data {
    if (!data) {
        return SDImageFormatUndefined;
    }
    
    // File signatures table: http://www.garykessler.net/library/file_sigs.html
    uint8_t c;
    [data getBytes:&c length:1];
    switch (c) {
        case 0xFF:
            return SDImageFormatJPEG;
        case 0x89:
            return SDImageFormatPNG;
        case 0x47:
            return SDImageFormatGIF;
        case 0x49:
        case 0x4D:
            return SDImageFormatTIFF;
        case 0x52: {
            if (data.length >= 12) {
                //RIFF....WEBP
                NSString *testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(0, 12)] encoding:NSASCIIStringEncoding];
                if ([testString hasPrefix:@"RIFF"] && [testString hasSuffix:@"WEBP"]) {
                    return SDImageFormatWebP;
                }
            }
            break;
        }
        case 0x00: {
            if (data.length >= 12) {
                //....ftypheic ....ftypheix ....ftyphevc ....ftyphevx
                NSString *testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(4, 8)] encoding:NSASCIIStringEncoding];
                if ([testString isEqualToString:@"ftypheic"]
                    || [testString isEqualToString:@"ftypheix"]
                    || [testString isEqualToString:@"ftyphevc"]
                    || [testString isEqualToString:@"ftyphevx"]) {
                    return SDImageFormatHEIC;
                }
                if ([testString isEqualToString:@"ftypmif1"] || [testString isEqualToString:@"ftypmsf1"]) {
                    return SDImageFormatHEIF;
                }
            }
            break;
        }
    }
    return SDImageFormatUndefined;
}
iOS中的圖片加載過程

圖片顯示到屏幕主要是依靠CPU和GPU協(xié)同合作完成的雌团。分工具體如下:

  • CPU: 計算視圖frame燃领,圖片解碼,需要繪制紋理圖片通過數(shù)據(jù)總線交給GPU
  • GPU: 紋理混合锦援,頂點變換與計算,像素點的填充計算猛蔽,渲染到幀緩沖區(qū)。
  • 時鐘信號:垂直同步信號V-Sync / 水平同步信號H-Sync灵寺。
  • iOS設(shè)備雙緩沖機制:顯示系統(tǒng)通常會引入兩個幀緩沖區(qū)曼库,雙緩沖機制。

開發(fā)中拿到的圖片大部分都是jpg替久,png凉泄,gif等經(jīng)過格式化的文件躏尉,這些圖片都是被壓縮過的蚯根,在渲染到屏幕之前,都需要先解碼成bitmap(未壓縮的位圖)
以UIImageView顯示一張圖片為例,會經(jīng)過以下步驟:
1.使用 +imageWithContentsOfFile:方法從磁盤中加載一張圖片颅拦,這個時候的圖片并沒有解壓縮蒂誉。
2.將生成的image賦值給UIImageView。
3.接著一個隱式的 CATransaction 捕獲到了 UIImageView 圖層樹的變化距帅。
4.在主線程的下一個 runloop 到來時右锨,Core Animation 提交了這個隱式的 transaction ,這個過程可能會對圖片進(jìn)行 copy 操作碌秸,而受圖片是否字節(jié)對齊等因素的影響绍移,這個 copy 操作可能會涉及以下部分或全部步驟:

*   分配內(nèi)存緩沖區(qū)用于管理文件 IO 和解壓縮操作;
*   將文件數(shù)據(jù)從磁盤讀到內(nèi)存中讥电;
*   將壓縮的圖片數(shù)據(jù)解碼成未壓縮的位圖形式蹂窖,這是一個非常耗時的 CPU 操作;
*   最后 `Core Animation` 中`CALayer`使用未壓縮的位圖數(shù)據(jù)渲染 `UIImageView` 的圖層恩敌。
*   CPU計算好圖片的Frame,對圖片解壓之后.就會交給GPU來做圖片渲染渲染流程
*   GPU獲取獲取圖片的坐標(biāo)
*   將坐標(biāo)交給頂點著色器(頂點計算)
*   將圖片光柵化(獲取圖片對應(yīng)屏幕上的像素點)
*   片元著色器計算(計算每個像素點的最終顯示的顏色值)
*   從幀緩存區(qū)中渲染到屏幕上

圖片渲染到屏幕的過程: 讀取文件->cpu計算Frame->cpu圖片解碼->解碼后紋理圖片位圖數(shù)據(jù)通過數(shù)據(jù)總線交給GPU->GPU獲取圖片F(xiàn)rame->頂點變換計算->光柵化->根據(jù)紋理坐標(biāo)獲取每個像素點的顏色值(如果出現(xiàn)透明值需要將每個像素點的顏色*透明度值)->渲染到幀緩存區(qū)->渲染到屏幕

圖片的解壓縮是非常耗時的cpu操作瞬测,且默認(rèn)是在主線程執(zhí)行。當(dāng)在快速滑動的列表上有多張圖片顯示時纠炮,應(yīng)用的響應(yīng)性就會比較差月趟。

解壓縮是否必須

是必須的。因為將圖片渲染到屏幕之前恢口,必須拿到圖片的原始像素數(shù)據(jù)bitmap(位圖)孝宗。之前提到的jpg,png耕肩,gif碳褒,webp都是一種壓縮的位圖圖形格式。只不過png是無損壓縮看疗,jpg是有損壓縮沙峻。

位圖
就是bitmap文件,它是一種非壓縮的圖片格式两芳。所謂的非壓縮摔寨,就是圖片每個像素的原始信息在存儲器中依次排列,比如1920*1080像素的 bitmap 圖片怖辆,每個像素由 RGBA 四個字節(jié)表示顏色是复,那么它的體積就是 1920 * 1080 * 4 / 8 = 1012.5kb。
由于 bitmap 簡單順序存儲圖片的像素信息竖螃,它可以不經(jīng)過解碼就直接被渲染到 UI 上淑廊。實際上,其它格式的圖片都需要先被首先解碼為 bitmap特咆,然后才能渲染到界面上季惩。

圖片解壓縮的過程其實就是將圖片的二進(jìn)制數(shù)據(jù)轉(zhuǎn)換成像素數(shù)據(jù)的過程

如何做到性能優(yōu)化:

未解壓縮的圖片將要渲染到屏幕時,cpu會在在主線程解壓縮,而解壓縮過的圖片画拾,就不會再去解壓縮了啥繁。
常見的解決方案是在子線程提前對圖片進(jìn)行解壓縮。強制解壓縮的原理就是對圖片進(jìn)行重新繪制青抛,得到一張新的解壓縮后的位圖旗闽。
YYImage\SDWebImage主流框架的也大致是這么實現(xiàn)的。

// 1. 從 UIImage 對象中獲取 CGImageRef 的引用蜜另。這兩個結(jié)構(gòu)是蘋果在不同層級上對圖片的表示方式适室,UIImage 屬于 UIKit,是 UI 層級圖片的抽象举瑰,用于圖片的展示亭病;CGImageRef 是 QuartzCore 中的一個結(jié)構(gòu)體指針,用C語言編寫嘶居,用來創(chuàng)建像素位圖罪帖,可以通過操作存儲的像素位來編輯圖片。這兩種結(jié)構(gòu)可以方便的互轉(zhuǎn):
CGImageRef imageRef = image.CGImage;

// 2. 調(diào)用 UIImage 的 +colorSpaceForImageRef: 方法來獲取原始圖片的顏色空間參數(shù)邮屁。
CGColorSpaceRef colorspaceRef = [UIImage colorSpaceForImageRef:imageRef];
        
size_t width = CGImageGetWidth(imageRef);
size_t height = CGImageGetHeight(imageRef);

// 3. 計算圖片解碼后每行需要的比特數(shù)整袁,由兩個參數(shù)相乘得到:每行的像素數(shù) width,和存儲一個像素需要的比特數(shù)4(這里的4佑吝,其實是由每張圖片的像素格式和像素組合來決定的)
size_t bytesPerRow = 4 * width;

// 4. 最關(guān)鍵的函數(shù):調(diào)用 CGBitmapContextCreate() 方法坐昙,生成一個空白的圖片繪制上下文,我們傳入了上述的一些參數(shù)芋忿,指定了圖片的大小炸客、顏色空間、像素排列等等屬性戈钢。
CGContextRef context = CGBitmapContextCreate(NULL,
                                             width,
                                             height,
                                             kBitsPerComponent,
                                             bytesPerRow,
                                             colorspaceRef,
                                             kCGBitmapByteOrderDefault|kCGImageAlphaNoneSkipLast);
if (context == NULL) {
    return image;
}
        
// 5. 調(diào)用 CGContextDrawImage() 方法痹仙,將未解碼的 imageRef 指針內(nèi)容,寫入到我們創(chuàng)建的上下文中殉了,這個步驟开仰,完成了隱式的解碼工作。
CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef);

// 6. 從 context 上下文中創(chuàng)建一個新的 imageRef薪铜,這是解碼后的圖片了众弓。
CGImageRef newImageRef = CGBitmapContextCreateImage(context);

// 7. 從 imageRef 生成供UI層使用的 UIImage 對象,同時指定圖片的 scale 和 orientation 兩個參數(shù)隔箍。
UIImage *newImage = [UIImage imageWithCGImage:newImageRef
                                        scale:image.scale
                                  orientation:image.imageOrientation];

CGContextRelease(context);
CGImageRelease(newImageRef);

return newImage;

YYImage 中對圖片的解壓縮過程與上述完全一致谓娃,只是傳遞給 CGBitmapContextCreate 函數(shù)的部分參數(shù)存在細(xì)微的差別

性能對比:

  • 在解壓PNG圖片,SDWebImage>YYImage
  • 在解壓JPEG圖片,SDWebImage<YYImage

這樣就在子線程中對圖片進(jìn)行了強制解碼,回調(diào)給主線程使用蜒滩,從而大大提高了圖片的渲染效率滨达。這也是現(xiàn)在主流三方庫圖片解碼的最佳實踐奶稠。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市弦悉,隨后出現(xiàn)的幾起案子窒典,更是在濱河造成了極大的恐慌蟆炊,老刑警劉巖稽莉,帶你破解...
    沈念sama閱讀 221,430評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異涩搓,居然都是意外死亡污秆,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,406評論 3 398
  • 文/潘曉璐 我一進(jìn)店門昧甘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來良拼,“玉大人,你說我怎么就攤上這事充边∮雇疲” “怎么了?”我有些...
    開封第一講書人閱讀 167,834評論 0 360
  • 文/不壞的土叔 我叫張陵浇冰,是天一觀的道長贬媒。 經(jīng)常有香客問我,道長肘习,這世上最難降的妖魔是什么际乘? 我笑而不...
    開封第一講書人閱讀 59,543評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮漂佩,結(jié)果婚禮上脖含,老公的妹妹穿的比我還像新娘。我一直安慰自己投蝉,他們只是感情好养葵,可當(dāng)我...
    茶點故事閱讀 68,547評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著瘩缆,像睡著了一般港柜。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上咳榜,一...
    開封第一講書人閱讀 52,196評論 1 308
  • 那天夏醉,我揣著相機與錄音,去河邊找鬼涌韩。 笑死畔柔,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的臣樱。 我是一名探鬼主播靶擦,決...
    沈念sama閱讀 40,776評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼腮考,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了玄捕?” 一聲冷哼從身側(cè)響起踩蔚,我...
    開封第一講書人閱讀 39,671評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎枚粘,沒想到半個月后馅闽,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,221評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡馍迄,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,303評論 3 340
  • 正文 我和宋清朗相戀三年福也,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片攀圈。...
    茶點故事閱讀 40,444評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡暴凑,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出赘来,到底是詐尸還是另有隱情现喳,我是刑警寧澤,帶...
    沈念sama閱讀 36,134評論 5 350
  • 正文 年R本政府宣布犬辰,位于F島的核電站嗦篱,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏忧风。R本人自食惡果不足惜默色,卻給世界環(huán)境...
    茶點故事閱讀 41,810評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望狮腿。 院中可真熱鬧腿宰,春花似錦、人聲如沸缘厢。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,285評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽贴硫。三九已至椿每,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間英遭,已是汗流浹背间护。 一陣腳步聲響...
    開封第一講書人閱讀 33,399評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留挖诸,地道東北人汁尺。 一個月前我還...
    沈念sama閱讀 48,837評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像多律,于是被迫代替她去往敵國和親痴突。 傳聞我的和親對象是個殘疾皇子搂蜓,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,455評論 2 359