iOS圖片解碼以及YYImage源碼探索

1.圖片加載原理

? ? 1.磁盤上的圖片文件大小和加載到imageView上的圖片大小的關(guān)系

準(zhǔn)備好的圖片

如圖所示 當(dāng)沒有加載圖片的時(shí)候此時(shí)內(nèi)存消耗為17.8M

當(dāng)我們點(diǎn)擊Push按鈕加載圖片到imageView中時(shí),此時(shí)內(nèi)存的占用缺達(dá)到了35M,比未加載圖片時(shí)多出了17.2M,但是我們的圖片如圖1所示只有126KB,這又是這回事呢

總結(jié):磁盤上的圖片文件大小和加載到imageView上的圖片大小沒有關(guān)系。

問題:加載到imageView上的圖片大小和什么有關(guān)系呢?

回到:由兩方面決定:1:圖片的尺寸大小 2:Color Profile(P3廣色域,display3,aRGB,sRGB)

圖片加載到imageView上實(shí)際的大小 1080*1920*4(aRGB) = 8294400b具體是不是這樣的呢

通過我們的打印得到的結(jié)果和我們的猜想完全對應(yīng),由于這里img和imgView的原因圖片被加載了2次所有內(nèi)存會多出17.2M,同時(shí)我們也驗(yàn)證了磁盤上的圖片文件大小和加載到imageView上的圖片大小沒有關(guān)系。

同時(shí)我們也可以通過Allocations來驗(yàn)證


通過圖片是否加載前后兩次的快照的內(nèi)存增長占用情況也可以得出相同的結(jié)果

? ? 2.imageNamed:這個(gè)方法到底做了什么呢?

在圖片加載時(shí)在內(nèi)存中創(chuàng)建一個(gè)Data Buffer(磁盤圖片加載進(jìn)內(nèi)存中的一個(gè)緩沖區(qū))儲存圖片壓縮之后的元數(shù)據(jù)(png,jpg等)通過解碼Decode之后在內(nèi)存中創(chuàng)建Image Buffer儲存圖片的像素信息,拿到像素信息后就交給GPU,GPU經(jīng)過計(jì)算放到當(dāng)前的Frame Buffer中,最后通過硬件把當(dāng)前的Frame Buffer中的信息渲染到屏幕中

1.Data Buffer:內(nèi)存中存儲圖片的原始信息(jpg,png等)

2.Image Buffer:內(nèi)存中儲存圖片的像素信息(大小和當(dāng)前圖片大小正比 寬*高*Color Profile系數(shù))

3.Frame Buffer:顯存中(Video RAM)

解碼過程:Data Buffer ->生成Image Buffer -> 上傳給GPU -> Frame Buffer -> V-sync 每秒60/120次更新屏幕

2.圖片解碼方式

在iOS中除了通過imageNamed:這種隱式解碼還有其他的方式可以進(jìn)行解碼

1.隱式解碼

????1.imageNamed:

2.主動(dòng)解碼

? ? 1.Core Graphics

????2.ImageIO

? ? 3.CGContext


Core Graphics解碼

通過Core Graphics就把在主線程解碼的操作放到了子線程中,下面我們利用Timer Profiler驗(yàn)證下

在主線程中我們沒有發(fā)現(xiàn)有圖片解碼的操作,這跟我們預(yù)想的一樣

ImageIO解碼
CGContext解碼

相較于其他使用CGBitmapContextCreate函數(shù)解碼最高

CGBitmapContextCreate(<#void * _Nullable data#>, <#size_t width#>, <#size_t height#>, <#size_t bitsPerComponent#>, <#size_t bytesPerRow#>, <#CGColorSpaceRef? _Nullable space#>, <#uint32_t bitmapInfo#>)參數(shù)所代表的意義

data:所需要的內(nèi)存空間,如果不需要操作這片內(nèi)存空間可以直接傳nil

heigh/width : 高和寬

bitsPerComponent:像素點(diǎn)RGB(8bit)

bytesPerRow:每一行使用的字節(jié)數(shù)( 4 * width)最后是64的整數(shù)倍---字節(jié)對齊

space:顏色空間

bitmapInfo:RGBA順序, 大小端的模式

3.YYImage實(shí)現(xiàn)源碼解析

YYImage.h

1.YYImage繼承自UIImage,并遵守了YYAnimatedImage協(xié)議

2.重寫了UIimage加載圖片的方法

????+ (nullable YYImage *)imageNamed:(NSString *)name; // no cache!

????+ (nullable YYImage *)imageWithContentsOfFile:(NSString *)path;

????+ (nullable YYImage *)imageWithData:(NSData *)data;

????+ (nullable YYImage *)imageWithData:(NSData *)data scale:(CGFloat)scale;

3.添加了一些屬性

? ? 1.animatedImageType:圖片類型

????2.animatedImageData:原圖片Data

? ? 3.animatedImageMemorySize:圖片占用的內(nèi)存空間(多幀圖片使用)

? ? 4.preloadAllAnimatedImageFrames:將所有幀圖像預(yù)加載到內(nèi)存。

YYImage入口函數(shù)

通過圖片的名字在Bundle中獲取到圖片的路徑并把圖片轉(zhuǎn)成NSData傳入到下個(gè)方法中

首先初始化了一個(gè)信號量,接著把接下來的所生成的對象加入到了自動(dòng)釋放池中

YYImageDecoder *decoder = [YYImageDecoder decoderWithData:data scale:scale];

獲取ImageSource

YYImageFrame *frame = [decoder frameAtIndex:0 decodeForDisplay:YES];

對圖片進(jìn)行手動(dòng)解碼,并生成了Image Buffer(第一幀)

如果是多幀圖計(jì)算了內(nèi)存占用量否則直接返回

1.對傳入的data判空處理

2.Image解碼器的初始化

3.手動(dòng)解碼?- (BOOL)updateData:(NSData *)data final:(BOOL)final

4.判斷解碼是否成功


這里的代碼就很簡單

1.加了一把遞歸鎖 ----- 防止 漸進(jìn)式解碼重復(fù)進(jìn)入同一線程

? ? 漸進(jìn)式解碼:

? ??CGImageSourceCreateIncremental(<#CFDictionaryRef? _Nullable options#>)

? ??CGImageSourceUpdateData(<#CGImageSourceRef? _Nonnull isrc#>, <#CFDataRef? _Nonnull data#>, <#bool final#>)

2.調(diào)用一個(gè)私有函數(shù),返回是否獲取imageSource成功

1.YYImageType type = YYImageDetectType((__bridge CFDataRef)data);獲取圖片的格式(png,jpg等)

2.- (void)_updateSource:獲取ImageSource

根據(jù)圖片格式的不同,分別采用不同的解碼方式

1.判斷源是否為nil,如果為空創(chuàng)建輸入源 這里創(chuàng)建輸入源用了兩種方式,在前文中已經(jīng)介紹過了

2.判斷圖片是否為GIF圖片

由于代碼太長沒截完整

根據(jù)前文中獲取到的圖片有多少幀進(jìn)行一個(gè)循環(huán),獲取每一幀的圖片信息

接下來我們來到關(guān)鍵的地方解碼圖片:YYImageFrame *frame = [decoder frameAtIndex:0 decodeForDisplay:YES];

這里跟前文中?- (BOOL)updateData:(NSData *)data final:(BOOL)final類似加了一把遞歸鎖,這里就不做贅述,繼續(xù)往下走

1.首先判斷了圖片是否需要混合

2.調(diào)用了一個(gè)私有函數(shù)獲取到ImageRef ?CGImageSourceCreateImageAtIndex(_source, index, (CFDictionaryRef)@{(id)kCGImageSourceShouldCache:@(YES)});

3.調(diào)用YYCGImageCreateDecodedCopy(imageRef, YES);函數(shù)

這這里我們就看到了一些比較熟悉的函數(shù)調(diào)用,原來它也是通過調(diào)用原生底層的函數(shù)對圖片進(jìn)行解碼

這個(gè)方法里也是這樣


總結(jié):

YYImage

1.創(chuàng)建了YYImageDecoder(解碼操作)

? ? 1.獲取圖片的元數(shù)據(jù) (_updateSource)

? ? 2.獲取YYImageDecoderFrame (圖片信息)

2.frameAtIndex: decodeForDisplay:(解碼操作)線程安全,此時(shí)已經(jīng)獲取到了解碼后數(shù)據(jù)

4.YYAnimatedImageView解析

1.YYAnimatedImageView繼承自UIImageView 用于展示動(dòng)圖

2.autoPlayAnimatedImage:默認(rèn)YES 自動(dòng)播放動(dòng)圖

3.currentAnimatedImageIndex:當(dāng)前展示的圖片是動(dòng)圖的第幾幀

4.currentIsPlayingAnimation:當(dāng)前是否在播放中

5.runloopMode:當(dāng)前runloop的模式

6.maxBufferSize:最大容積

首先我們找到這個(gè)類的入口函數(shù)

1.設(shè)置了runloopMode:NSRunLoopCommonModes,為了播放動(dòng)圖時(shí)不受其他事件的影響

2.把a(bǔ)utoPlayAnimatedImage設(shè)置為YES

重寫了Image的set方法作為下一步的入口

1.停止動(dòng)畫

2.調(diào)用resetAnimated重置動(dòng)畫

3.imageChanged:圖片改變

1.詢問當(dāng)前的協(xié)議[newVisibleImage conformsToProtocol:@protocol(YYAnimatedImage)])

2.獲取當(dāng)前動(dòng)圖的下一幀

3.已經(jīng)圖片的幀

4.[self setNeedsDisplay];在下一個(gè)runloop到來時(shí)對圖層樹進(jìn)行更改

5.[self didMoved]

判斷當(dāng)前圖片是否需要自動(dòng)播放

1.創(chuàng)建一個(gè)定時(shí)器用于播放動(dòng)圖

注:為什么使用CADisplayLink而不使用NSTimer,CADisplayLink根據(jù)屏幕的刷新頻率來執(zhí)行事件,而NSTimer在預(yù)先設(shè)置的時(shí)間點(diǎn)上執(zhí)行事件,CADisplayLink比NSTimer跟精準(zhǔn)

2.在后臺現(xiàn)場異步釋放對象的策略

1.當(dāng)緩存區(qū)命中時(shí)進(jìn)行播放圖片

2.當(dāng)未命中緩沖區(qū)時(shí),證明需要進(jìn)行解碼


總結(jié):YYAnimatedImageView

1.動(dòng)圖的播放 -> 定時(shí)器的實(shí)現(xiàn)

2.根據(jù)index讀取下一幀nextIndex

3.去緩沖區(qū)里面取數(shù)據(jù)(_timer到了當(dāng)前幀的時(shí)間)

4.setNeedsDisplay 更新視圖

5.異步子線程進(jìn)行解碼操作

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容