技术经验解读:YYImage实现思路源码分析(图片解压缩原理)

简介: 技术经验解读:YYImage实现思路源码分析(图片解压缩原理)

2018年04月09日 18:00:13


阅读数:119


YYKit组件之一---->YYImage 图像处理


移动端图片格式调研


图片处理的小技巧


YYWebImage源码分析


YYModel源码分析


YYText源码分析


核心思路--->图片解码 (二进制数据位图)


雷纯峰的分析


这段是前言,介绍下图片是如何解码的。不想看到的可以直接无视


核心代码:


【objc】 view plain copy


【_decoder frameAtIndex:index decodeForDisplay:YES】----->YYCGImageCreateDecodedCopy


左边是外部暴露的解码方法,右边是核心解码方法


我们首先要知道,如果最普通的UIImageView的图片UIImage创建资源赋值,图片是没有解码的,只有当图片被被赋值给UIImageView的时候,Runloop捕获到事件,才会进行解压缩,其中会把二进制压缩的数据,解压成没有压缩的位图,这里就是最耗时的操作


我这里只是简单的说下我自己理解的流程,具体验证可以看雷哥的博客


既然那么耗时,为什么一定要解压缩才可以显示?那你得明白位图数据和二进制数据的区别了。


比如一张10kb的图,我们有data信息,也就是平时看到的PNG或者JPEG等后缀的格式,其中PNG支持alpha通道,无损压缩,JPEG是支持有损压缩的 图片压缩格式介绍 ,有损无损无非就是把多余的通过代码压进去,可以看看这个文章


那么PNG还是JPEG,只是位图的压缩形式罢了。一张PNG的图,解压缩出来就是原始位图,里面装载着像素信息,颜色信息等,这才是最原始的解压后的图,只有这样,所有的信息具备,才能被渲染到屏幕上,因此拿到的图片只能解压缩才可以显示(就是必然要耗时),既然一定要解压,耗时,不能卡在主线程,那就拿到子线程解压,把解压完的图片返回之后,再次渲染的时候,捕捉到已经解压了,就不需要在主线程解压了,直接显示。这也是所有第三方图片框架下载的核心。平时如果你不在意,你压根不知道他做了什么性能优化。


以上就是原理知识点


解压位图官方核心代码


【objc】 view plain copy


/ Create a bitmap context. The context draws into a bitmap which is width' </p> <p> pixels wide andheight' pixels high. The number of components for each


pixel is specified by space', which may also specify a destination color </p> <p> profile. The number of bits for each component of a pixel is specified by </p> <p>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 leastwidth bytes


per pixel' bytes; in addition, bytesPerRow' must be an integer multiple </p> <p> 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 </p> <p> data for context is allocated automatically and freed when the context is </p> <p> 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(voidvoid 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);


data :如果不为 NULL ,那么它应该指向一块大小至少为 bytesPerRow Height</code> 字节的内存;如果 为 NULL ,那么系统就会为我们自动分配和释放所需的内存,所以一般指定 NULL 即可;


Width</code> 和 Height</code> :位图的宽度和高度,分别赋值为图片的像素宽度和像素高度即可;


bitsPerComponent :像素的每个颜色分量使用的 bit 数,在 RGB 颜色空间下指定 8 即可;


bytesPerRow :位图的每一行使用的字节数,大小至少为 width bytes per pixel 字节。有意思的是,当我们指定 0 时,系统不仅会为我们自动计算,而且还会进行 cache line alignment 的优化


space :就是我们前面提到的颜色空间,一般使用 RGB 即可;


bitmapInfo :就是我们前面提到的位图的布局信息。


以上是雷哥总结出来的参数介绍,下面看看YY里面的调用解压


【objc】 view plain copy


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


通过CGBitmapContextCreate创建位图上下文


通过CGContextDrawImage把原始位图绘制到上下文


CFDataRef rawData = CGDataProviderCopyData(CGImageGetDataProvider(image.CGImage));该方法可以获取到原始位图信息


CGBitmapContextCreateImage创建一个新的解压后的位图


通过资源的读取到屏幕渲染之间,我们不做处理,系统的解压是在主线程的,因此我们穿插了强制解压,放在异步线程处理,会让性能有着显著的提升。YY,SD,FLA都是这个思路


特性


支持以下类型动画图像的播放/编码/解码:


WebP, APNG, GIF。


支持以下类型静态图像的显示/编码/解码:


WebP, PNG, GIF, JPEG, JP2, TIFF, BMP, ICO, ICNS。


支持以下类型图片的渐进式/逐行扫描/隔行扫描解码:


PNG, GIF, JPEG, BMP。


支持多张图片构成的帧动画播放,支持单张图片的 sprite sheet 动画。


高效的动态内存缓存管理,以保证高性能低内存的动画播放。


完全兼容 UIImage 和 UIImageView,使用方便。


保留可扩展的接口,以支持自定义动画。


每个类和方法都有完善的文档注释。


概览


流程


YYImage : UIImage的子类,遵守 YYAnimatedImage 协议,帧图片,编解码,帧预加载等高级特性,支持WebP,APNG和GIF的编解码


YYFrameImage : 能够显示帧动画,仅支持png,jpeg 格式


YYSpriteSheetImage : 是用来做Spritesheet动画显示的图像类,也是UIImage的子类


YYImageCoder : 图像的编码和解码功能类,YYImage底层支持,YYImageEncoder负责编码,YYImageDecoder 负责解码,YYImageFrame 负责管理帧图像信息,_YYImageDecoderFrame 内部私有类是其子类,UIImage+YYImageCoder提供了一些便利方法


YYAnimatedImageView: UIImageView 子类,用于播放图像动画


1.第一步 YYImage的初始化


根据流程走一遍,以YYImage为例,先创建一个YYImage的对象供后续使用


【html】 view plain copy


YYImage image = 【YYImage imageNamed:name】;


这里入口函数,接着会根据一些后缀,或者没有给出来判断出扩展名


@【@"", @"png", @"jpeg", @"jpg", @"gif", @"webp", @"apng"】


最后来根据以下方法初始化YYImage对象


【html】 view plain copy


- (instancetype)initWithData:(NSData )data scale:(CGFloat)scale {


if (data.length == 0) return nil;


if (scale <= 0) scale = 【UIScreen mainScreen】.scale;


_preloadedLock = dispatch_semaphore_create(1);


@autoreleasepool {


// 解码器创建


YYImageDecoder decoder = 【YYImageDecoder decoderWithData:data scale:scale】;


// 根据index从解码器的数组里面提取出 _YYImageDecoderFrame 然后对图片源根据index解码出对应的帧图片存储到frame的image字段返回


YYImageFrame frame = 【decoder frameAtIndex:0 decodeForDisplay:YES】;


// 上一个方法的解码,赋值 初始化拿出来的就是第一帧


UIImage image = frame.image;


if (!image) return nil;


self = 【self initWithCGImage:image.CGImage scale:decoder.scale orientation:image.imageOrientation】;


if (!self) return nil;


_animatedImageType = decoder.type;


if (decoder.frameCount

_decoder = decoder;


_bytesPerFrame = CGImageGetBytesPerRow(image.CGImage) CGImageGetHeight(image.CGImage);


_animatedImageMemorySize = _bytesPerFrame decoder.frameCount;


}


self.yy_isDecodedForDisplay = YES;


}


return self;


}


先来看一下YYImageDecoder的私有变量


【html】 view plain copy


@implementation YYImageDecoder {


pthread_mutex_t _lock; // recursive lock 递归锁 初始化调用 更新图像数据源加递归锁


BOOL _sourceTypeDetected; // 是否推测图像源类型


CGImageSourceRef _source; // 图像源


yy_png_info _apngSource; // 如果判定图像为 YYImageTypePNG 则会以 APNG 更新图像源


#if YYIMAGE_WEBP_ENABLED


WebPDemuxer _webpSource; // 如果判定图像为 YYImageTypeWebP 则会议 WebP 更新图像源


#endif


UIImageOrientation _orientation; // 绘制方向


dispatch_semaphore_t _framesLock; // 针对于图像帧的锁 这种不长时间阻塞线程的线程安全可以用信号量 frame操作锁


NSArray _frames; ///< Array[span class="tag-name">GGImageDecoderFrame

BOOL _needBlend; // 是否需要混合 WebP 和 APNG来用的


NSUInteger _blendFrameIndex; // 从帧索引混合到当前帧


CGContextRef _blendCanvas; // 混合画布


}


解码器根据Data源初始化的核心代码


【html】 view plain copy


- (void)_updateSourceImageIO {


// 宽 高 初始方向 循环次数


_width = 0;


_height = 0;


_orientation = UIImageOrientationUp;


_loopCount = 0;


// 清楚原先解码器的数据


dispatch_semaphore_wait(_framesLock, DISPATCH_TIME_FOREVER);


_frames = nil;


dispatch_semaphore_signal(_framesLock);


// 处理图像源


if (!_source) {


if (_finalized) {


_source = CGImageSourceCreateWithData((bridge CFDataRef)_data, NULL);


} else {


_source = CGImageSourceCreateIncremental(NULL);


if (_source) CGImageSourceUpdateData(_source, (bridge CFDataRef)_data, false);


}


} else {


CGImageSourceUpdateData(_source, (bridge CFDataRef)_data, _finalized);


}


if (!_source) return;


// 获取图像帧数


_frameCount = CGImageSourceGetCount(_source);


if (_frameCount == 0) return;


if (!_finalized) { // ignore multi-frame before finalized


_frameCount = 1;


} else {


// PNG一帧


if (_type == YYImageTypePNG) { // use custom apng decoder and ignore multi-frame


_frameCount = 1;


}


// GIF多帧


if (_type == YYImageTypeGIF) { // get gif loop count


// 获取数据源属性字典


CFDictionaryRef properties = CGImageSourceCopyProperties(source, NULL);


// 属性字典获取到


if (properties) {


// 根据Key kCGImagePropertyGIFDictionary 获取到GIF下的字典属性


CFDictionaryRef gif = CFDictionaryGetValue(properties, kCGImagePropertyGIFDictionary);


// 获取到gif 字典


if (gif) {


// 获取循环次数 根据Key kCGImagePropertyGIFLoopCount


CFTypeRef loop = CFDictionaryGetValue(gif, kCGImagePropertyGIFLoopCount);


// //代码效果参考:http://www.lyjsj.net.cn/wx/art_22932.html

loopCount 地址进去赋值

if (loop) CFNumberGetValue(loop, kCFNumberNSIntegerType, &_loopCount);


}


CFRelease(properties);


}


}


}


/


ICO, GIF, APNG may contains multi-frame.


多帧的情况下才会进来


/


NSMutableArray frames = 【NSMutableArray new】;


for (NSUInteger i = 0; i < _frameCount; i++) {


// 每一帧的对象属性 继承于YYImageFrame


_YYImageDecoderFrame frame = 【_YYImageDecoderFrame new】;


frame.index = i; // 当前索引


frame.blendFromIndex = i;


frame.hasAlpha = YES;


frame.isFullSize = YES;


【frames addObject:frame】;


// 根据数据源的索引获取属性字典 (刚才上面的获取方式是拿循环次数的时候GIF专用key,这里有多种情况,就根据下标拿)


CFDictionaryRef properties = CGImageSourceCopyPropertiesAtIndex(_source, i, NULL);


if (properties) {


NSTimeInterval duration = 0;


NSInteger orientationValue = 0, width = 0, height = 0;


CFTypeRef value = NULL;


// 获取宽度


value = CFDictionaryGetValue(properties, kCGImagePropertyPixelWidth);


if (value) CFNumberGetValue(value, kCFNumberNSIntegerType, &width);


// 获取高度


value = CFDictionaryGetValue(properties, kCGImagePropertyPixelHeight);


if (value) CFNumberGetValue(value, kCFNumberNSIntegerType, &height);


// 如果是GIF


if (_type == YYImageTypeGIF) {


&

相关文章
|
JavaScript 前端开发 算法
简化文件上传流程:学习如何封装Vue2拖拽上传组件
简化文件上传流程:学习如何封装Vue2拖拽上传组件
283 0
|
iOS开发 MacOS Perl
解决Xcode运行IOS报错:redefinition of module ‘Firebase‘和could not build module ‘CoreFoundation‘
解决Xcode运行IOS报错:redefinition of module ‘Firebase‘和could not build module ‘CoreFoundation‘
795 4
|
7月前
|
JSON 安全 网络协议
HTTP/HTTPS协议(请求响应模型、状态码)
本文简要介绍了HTTP与HTTPS协议的基础知识。HTTP是一种无状态的超文本传输协议,基于TCP/IP,常用80端口,通过请求-响应模型实现客户端与服务器间的通信;HTTPS为HTTP的安全版本,基于SSL/TLS加密技术,使用443端口,确保数据传输的安全性。文中还详细描述了HTTP请求方法(如GET、POST)、请求与响应头字段、状态码分类及意义,并对比了两者在请求-响应模型中的安全性差异。
723 20
手势代理 shouldBeRequiredToFailByGestureRecognizer 和 shouldRequireFailureOfGestureRecognizer 的区别
手势代理 shouldBeRequiredToFailByGestureRecognizer 和 shouldRequireFailureOfGestureRecognizer 的区别
450 10
|
存储 数据可视化 数据挖掘
NumPy 1.26 中文官方指南(二)(2)
NumPy 1.26 中文官方指南(二)
307 0
|
10月前
|
存储 数据采集 数据格式
Python自动化Office文档处理全攻略
本文介绍如何使用Python自动化处理Word、Excel和PDF文档,提升办公效率。通过安装`python-docx`、`openpyxl`、`pandas`、`PyPDF2`和`pdfplumber`等库,可以轻松实现读取、修改、创建和批量处理这些文档。具体包括:自动化处理Word文档(如读取、修改内容、调整样式),Excel文档(如读取、清洗、汇总数据),以及PDF文档(如提取文本和表格数据)。结合代码示例和实战案例,帮助你掌握高效办公技巧,减少手动操作的错误率。
693 1
ly~
|
网络协议 算法 关系型数据库
C语言的应用
C 语言因其高效性和对硬件的直接访问能力,在多个领域有广泛应用。在系统软件领域,它被用于开发操作系统(如 Unix 和 Linux 的内核)和嵌入式系统(如汽车电子控制系统)。在游戏开发中,C 语言常用于构建游戏引擎的底层部分(如 Unity 和 Unreal Engine 的核心模块)及性能要求高的独立游戏。此外,C 语言也用于数据库管理系统(如 MySQL 和 PostgreSQL 的核心功能)和网络编程(如 TCP/IP 协议栈和网络服务器的核心模块)。
ly~
385 3
|
存储 编解码 算法
4K 蓝光与流媒体比较:哪个更好?
4K 蓝光提供无与伦比的图像和声音质量,使其成为重视沉浸式电影体验的爱好者的首选。另一方面,流媒体服务提供了触手可及的庞大内容库,可随时随地访问。在这篇文章中,我们将深入探讨每个选项的好处并提供全面的比较,以帮助你决定哪个更适合您的观看习惯和偏好。
956 3
|
弹性计算 应用服务中间件 网络安全
ECS服务器使用:SSL证书安装、配置和问题定位指南
本文简要介绍了SSL证书的生成与部署方法,包括使用OpenSSL生成自签名证书和从CA获取证书的步骤,以及在Apache和Nginx服务器上的配置方法。此外,还提供了测试证书是否生效的方法和常见问题的解决策略,帮助确保证书正确安装并解决调试过程中可能遇到的问题。
1222 0
|
机器学习/深度学习 人工智能 自然语言处理
【图像生成技术】人工智能在医疗健康领域的应用实例:图像生成技术的革新实践
在当今医疗健康的前沿阵地,人工智能(AI)技术正以前所未有的速度重塑着医疗服务的面貌,其中图像生成技术尤其在提升诊断精度、优化治疗策略及增强医疗教育方面展现出了巨大潜力。以下将通过一个简化的示例,展示如何利用深度学习模型,特别是生成对抗网络(GANs),来生成医学图像,并讨论其在实际医疗场景中的应用价值。
523 6