iOS图片解码以及YYImage源码探索

1.图片加载原理

    1.磁盘上的图片文件大小和加载到imageView上的图片大小的关系

准备好的图片

如图所示 当没有加载图片的时候此时内存消耗为17.8M

当我们点击Push按钮加载图片到imageView中时,此时内存的占用缺达到了35M,比未加载图片时多出了17.2M,但是我们的图片如图1所示只有126KB,这又是这回事呢

总结:磁盘上的图片文件大小和加载到imageView上的图片大小没有关系。

问题:加载到imageView上的图片大小和什么有关系呢?

回到:由两方面决定:1:图片的尺寸大小 2:Color Profile(P3广色域,display3,aRGB,sRGB)

图片加载到imageView上实际的大小 1080*1920*4(aRGB) = 8294400b具体是不是这样的呢

通过我们的打印得到的结果和我们的猜想完全对应,由于这里img和imgView的原因图片被加载了2次所有内存会多出17.2M,同时我们也验证了磁盘上的图片文件大小和加载到imageView上的图片大小没有关系。

同时我们也可以通过Allocations来验证


通过图片是否加载前后两次的快照的内存增长占用情况也可以得出相同的结果

    2.imageNamed:这个方法到底做了什么呢?

在图片加载时在内存中创建一个Data Buffer(磁盘图片加载进内存中的一个缓冲区)储存图片压缩之后的元数据(png,jpg等)通过解码Decode之后在内存中创建Image Buffer储存图片的像素信息,拿到像素信息后就交给GPU,GPU经过计算放到当前的Frame Buffer中,最后通过硬件把当前的Frame Buffer中的信息渲染到屏幕中

1.Data Buffer:内存中存储图片的原始信息(jpg,png等)

2.Image Buffer:内存中储存图片的像素信息(大小和当前图片大小正比 宽*高*Color Profile系数)

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

解码过程:Data Buffer ->生成Image Buffer -> 上传给GPU -> Frame Buffer -> V-sync 每秒60/120次更新屏幕

2.图片解码方式

在iOS中除了通过imageNamed:这种隐式解码还有其他的方式可以进行解码

1.隐式解码

    1.imageNamed:

2.主动解码

    1.Core Graphics

    2.ImageIO

    3.CGContext


Core Graphics解码

通过Core Graphics就把在主线程解码的操作放到了子线程中,下面我们利用Timer Profiler验证下

在主线程中我们没有发现有图片解码的操作,这跟我们预想的一样

ImageIO解码
CGContext解码

相较于其他使用CGBitmapContextCreate函数解码最高

CGBitmapContextCreate(<#void * _Nullable data#>, <#size_t width#>, <#size_t height#>, <#size_t bitsPerComponent#>, <#size_t bytesPerRow#>, <#CGColorSpaceRef  _Nullable space#>, <#uint32_t bitmapInfo#>)参数所代表的意义

data:所需要的内存空间,如果不需要操作这片内存空间可以直接传nil

heigh/width : 高和宽

bitsPerComponent:像素点RGB(8bit)

bytesPerRow:每一行使用的字节数( 4 * width)最后是64的整数倍---字节对齐

space:颜色空间

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

3.YYImage实现源码解析

YYImage.h

1.YYImage继承自UIImage,并遵守了YYAnimatedImage协议

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:图片占用的内存空间(多帧图片使用)

    4.preloadAllAnimatedImageFrames:将所有帧图像预加载到内存。

YYImage入口函数

通过图片的名字在Bundle中获取到图片的路径并把图片转成NSData传入到下个方法中

首先初始化了一个信号量,接着把接下来的所生成的对象加入到了自动释放池中

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

获取ImageSource

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

对图片进行手动解码,并生成了Image Buffer(第一帧)

如果是多帧图计算了内存占用量否则直接返回

1.对传入的data判空处理

2.Image解码器的初始化

3.手动解码 - (BOOL)updateData:(NSData *)data final:(BOOL)final

4.判断解码是否成功


这里的代码就很简单

1.加了一把递归锁 ----- 防止 渐进式解码重复进入同一线程

    渐进式解码:

    CGImageSourceCreateIncremental(<#CFDictionaryRef  _Nullable options#>)

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

2.调用一个私有函数,返回是否获取imageSource成功

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

2.- (void)_updateSource:获取ImageSource

根据图片格式的不同,分别采用不同的解码方式

1.判断源是否为nil,如果为空创建输入源 这里创建输入源用了两种方式,在前文中已经介绍过了

2.判断图片是否为GIF图片

由于代码太长没截完整

根据前文中获取到的图片有多少帧进行一个循环,获取每一帧的图片信息

接下来我们来到关键的地方解码图片:YYImageFrame *frame = [decoder frameAtIndex:0 decodeForDisplay:YES];

这里跟前文中 - (BOOL)updateData:(NSData *)data final:(BOOL)final类似加了一把递归锁,这里就不做赘述,继续往下走

1.首先判断了图片是否需要混合

2.调用了一个私有函数获取到ImageRef  CGImageSourceCreateImageAtIndex(_source, index, (CFDictionaryRef)@{(id)kCGImageSourceShouldCache:@(YES)});

3.调用YYCGImageCreateDecodedCopy(imageRef, YES);函数

这这里我们就看到了一些比较熟悉的函数调用,原来它也是通过调用原生底层的函数对图片进行解码

这个方法里也是这样


总结:

YYImage

1.创建了YYImageDecoder(解码操作)

    1.获取图片的元数据 (_updateSource)

    2.获取YYImageDecoderFrame (图片信息)

2.frameAtIndex: decodeForDisplay:(解码操作)线程安全,此时已经获取到了解码后数据

4.YYAnimatedImageView解析

1.YYAnimatedImageView继承自UIImageView 用于展示动图

2.autoPlayAnimatedImage:默认YES 自动播放动图

3.currentAnimatedImageIndex:当前展示的图片是动图的第几帧

4.currentIsPlayingAnimation:当前是否在播放中

5.runloopMode:当前runloop的模式

6.maxBufferSize:最大容积

首先我们找到这个类的入口函数

1.设置了runloopMode:NSRunLoopCommonModes,为了播放动图时不受其他事件的影响

2.把autoPlayAnimatedImage设置为YES

重写了Image的set方法作为下一步的入口

1.停止动画

2.调用resetAnimated重置动画

3.imageChanged:图片改变

1.询问当前的协议[newVisibleImage conformsToProtocol:@protocol(YYAnimatedImage)])

2.获取当前动图的下一帧

3.已经图片的帧

4.[self setNeedsDisplay];在下一个runloop到来时对图层树进行更改

5.[self didMoved]

判断当前图片是否需要自动播放

1.创建一个定时器用于播放动图

注:为什么使用CADisplayLink而不使用NSTimer,CADisplayLink根据屏幕的刷新频率来执行事件,而NSTimer在预先设置的时间点上执行事件,CADisplayLink比NSTimer跟精准

2.在后台现场异步释放对象的策略

1.当缓存区命中时进行播放图片

2.当未命中缓冲区时,证明需要进行解码


总结:YYAnimatedImageView

1.动图的播放 -> 定时器的实现

2.根据index读取下一帧nextIndex

3.去缓冲区里面取数据(_timer到了当前帧的时间)

4.setNeedsDisplay 更新视图

5.异步子线程进行解码操作

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容