ImageIO对外开放的对象有CGImageSourceRef、CGImageDestinationRef,不对外开放的对象有CGImageMetadataRef。CoreGraphics中经常与imageIO打交道的对象有CGImageRef和CGDataProvider,接下来看看这五个对象在创建一个UIImage中担任了哪些角色。

用TimeProfiler一步一步来看创建UIImage过程中内部调用的函数可以帮助我们解决问题,由于TimeProfiler统计函数栈为间隔一段时间统计一次,导致没有记录下所有函数的调用而且每次函数栈还可能不一致,所以没法精确判断函数栈是如何调用的,但是可以大概推测出每步做了什么。

从CFDataRef到UIImage代码如下

NSString *resource = [[NSBundle mainBundle] pathForResource:@"the_red_batman" ofType:@"png"];

NSData *data = [NSData dataWithContentsOfFile:resource options:0 error:nil];

CFDataRef dataRef = (__bridge CFDataRef)data;

CGImageSourceRef source = CGImageSourceCreateWithData(dataRef, nil);

CGImageRef cgImage = CGImageSourceCreateImageAtIndex(source, 0, nil);

UIImage *image = [UIImage imageWithCGImage:cgImage];

CGImagSourceCreateWithData

调用了内部函数_CGImageReadCreate,也就是说CGImageSourceRef跟读取图像数据有关。

CGImageSourceCreateImageAtIndex

调用了_cg_png_read_info和CGImageMetadataCreateMutable,在构建CGImageRef时,读取了图片的基础数据和元数据,基础数据中包括Image的header chunk,比如png的IHDR。元数据是由CGImageMetadataRef来抽象的。并且没有读取图片的其他数据,更没有做解码的动作。

有趣的是,如果调用CGImageSourceCopyPropertiesAtIndex

CGImageSourceCopyPropertiesAtIndex的内部函数调用了CGImageMetadataRef,如果再加上ImageIO/CGImageMetadata.h文件的注释

@description CGImageMetadata APIs allow clients to view and modify metadata

for popular image formats. ImageIO supports the EXIF, IPTC, and XMP

metadata specifications. Please refer to CGImageSource.h for functions to

read metadata from a CGImageSource, and CGImageDestination.h for functions to

write metadata to a CGImageDestination. CGImageDestinationCopyImageSource can

be used to modify metadata without recompressing the image.

说明CGImageMetadataRef抽象出图片中EXIF、IPTC、XMP格式的元数据插入字段,而若想获得CGImageMetadataRef必须要通过CGImageSourceRef。

同样,看看有关CGDataProviderRef的内部函数调用,代码如下

//将CGImageSourceRef改由CGDataProviderRef创建

CFDataRef dataRef = (__bridge CFDataRef)data;

CGDataProviderRef dataProvider = CGDataProviderCreateWithCFData(dataRef);

CGImageSourceRef source = CGImageSourceCreateWithDataProvider(dataProvider, nil);

//测试由CGImageRef获取CGDataProviderRef和由CGDataProviderRef创建CGImageRef

CGDataProviderRef newDataProvider = CGImageGetDataProvider(cgImage);

size_t width = CGImageGetWidth(cgImage);

size_t height = CGImageGetHeight(cgImage);

CGColorSpaceRef space = CGImageGetColorSpace(cgImage);

size_t bitsPerComponent = CGImageGetBitsPerComponent(cgImage);

size_t bitsPerPixel = CGImageGetBitsPerPixel(cgImage);

size_t bytesPerRow = CGImageGetBytesPerRow(cgImage);

CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(cgImage);

CGDataProviderRef newDataProvider = CGImageGetDataProvider(cgImage);

CGImageRef newImage = CGImageCreate(width, height, bitsPerComponent, bitsPerPixel, bytesPerRow, space, bitmapInfo, newDataProvider, NULL, false, kCGRenderingIntentDefault);

很可惜,没有找出有关CGDataProviderRef的函数调用。无法得出CGDataProviderRef做了什么。

看看有关CGImageDestinationRef的内部函数调用,代码和内部函数调用如下

CFMutableDataRef buffer = CFDataCreateMutable(kCFAllocatorDefault, 0);      CGImageDestinationRef destination = CGImageDestinationCreateWithData(buffer, kUTTypePNG, 1, NULL);

CGImageDestinationAddImage(destination, cgImage, nil);

CGImageDestinationFinalize(destination);

CGImageDestinationRef将图片数据写入目的地,并且负责做图片编码或者说图片压缩。

测试结论

CGImageSourceRef抽象了对读图像数据的通道,读取图像要通过它,它自己本身不读取图像的任何数据,在你调用CGImageSourceCopyPropertiesAtIndex的时候会才去读取图像元数据。

CGImageMetadataRef抽象出图片中EXIF、IPTC、XMP格式的元数据,通过CGImageSourceRef获取。

CGImageRef抽象了图像的基本数据和元数据,创建的时候会通过CGImageSourceRef去读取图像的基础数据和元数据,但没有读取图像的其他数据,没有做图片解码的动作。

CGDataProviderRef没有得出有用信息。

CGImageDestinationRef抽象写入图像数据的通道,写入图像要通过它,在写入图片的时候还负责图片的编码。

Image解码

可以看到从CFDataRef直到创建出UIImage,都没有调用过对图像解码的函数,只读取了一些图像基础数据和元数据。

Image解码发生在什么时候?在ImageIO/CGImageSource.h文件中kCGImageSourceShouldCache的上面其实有明确注释。

Specifies whether image decoding and caching should happen at image creation time.

The value of this key must be a CFBooleanRef. The default value is kCFBooleanFalse (image decoding will happen at rendering time).

如果不手动设置Image只会等到在屏幕上渲染时再解码。经过测试,确实如此。这个kCGImageSourceShouldCacheImmediately还不如起名为kCGImageSourceShouldDecodeImmediately。

Image解码到底在哪里做的?

如果在画布上渲染图片,图片一定是会被解码的。下列代码再跑测试

size_t width = CGImageGetWidth(cgImage);

size_t height = CGImageGetHeight(cgImage);

CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo(cgImage) & kCGBitmapAlphaInfoMask;

BOOL hasAlpha = NO;

if (alphaInfo == kCGImageAlphaPremultipliedLast ||

alphaInfo == kCGImageAlphaPremultipliedFirst ||

alphaInfo == kCGImageAlphaLast ||

alphaInfo == kCGImageAlphaFirst) {

hasAlpha = YES;

}

CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host;

bitmapInfo |= hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst;

CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, 0, CGColorSpaceCreateDeviceRGB(), bitmapInfo);

CGContextDrawImage(context, CGRectMake(0, 0, width, height), cgImage);

CGImageRef newImage = CGBitmapContextCreateImage(context);

所有调用的函数名中没有明显decompress或者decode字眼。而上面四个函数调用的频率是最高的。根据CGImageDestinationRef调用的png_compress_IDAT,猜测png_read_IDAT_data是做解码的函数。

以上是以png图片作为测试用例,下面看看jpeg图片的

很明显AppleJPEG的decode方法是做解码的函数。jpeg与png调用了两个同样函数,而不同的图片调了不同的解码函数。在画布上画图片的时候,会调用ImageProviderCopyImageBlockSetCallback设置callback,然后调用copyImageBlock,再调用设置的callback,但是解码函数是由copyImageBlock的调用的还是由callback调用的无法验证。

那ImageProviderCopyImageBlockSetCallback与CGDataProviderCopyData是否有关系?经过测试,CGDataProviderCopyData内部也会调用ImageProviderCopyImageBlockSetCallback和copyImageBlock。而且CGDataProviderCopyData得到的CFDataRef是解码过的像素数组。

结论:Image解码发生在CGDataProviderCopyData函数内部调用ImageProviderCopyImageBlockSetCallback设置的callback或者copyImageBlock函数,根据不同的图片格式调用的不同的方法中。

Image的初始化方法

imageWithData从内部函数的调用来看,通过CGImageSourceRef访问图像数据,创建CGImageRef。

imageWithContentsOfFile内部调用如下

文件通过mmap到内存然后通过CGImageSourceRef访问图像数据,创建CGImageRef。

imageNamed先从Bundle里找到资源路径,然后同样也是将文件mmap到内存,再通过CGImageSourceRef访问图像数据,创建CGImageRef。

Image的缓存

通过调用不同的UIImage初始化方法然后创建UIImageVIew展示到屏幕上,来看看不同方法是否有缓存的行为。

imageNamed在第二次展示相同image的时候没有调用imageIO的任何方法。

imageWithData和imageWithContentsOfFile在第二次在展示相同image的时候均调用了imageIO的解码方法。

而imageWithData和imageWithContentsOfFile初始化方法创建的UIImage只要不被释放,再次渲染不会调用imageIO解码方法。

结论为UIImage有两种缓存,一种是UIImage类的缓存,这种缓存保证imageNamed初始化的UIImage只会被解码一次。另一种是UIImage对象的缓存,这种缓存保证只要UIImage没有被释放,就不会再解码。

CGImageSourceCreateImageAtIndex方法的kCGImageSourceShouldCache选项指的是第二种缓存,而如果设置为false,我测试出来image再次渲染的时候仍没有进行解码,这有些奇怪。如果有同学详细知道怎么回事,还请赐教。

最后附上ImageIO的全家福

iOS中的imageIO与image解码的更多相关文章

  1. iOS中集成ijkplayer视频直播框架

    ijkplayer 是一款做视频直播的框架, 基于ffmpeg, 支持 Android 和 iOS, 网上也有很多集成说明, 但是个人觉得还是不够详细, 在这里详细的讲一下在 iOS 中如何集成ijk ...

  2. iOS中使用RSA对数据进行加密解密

    RSA算法是一种非对称加密算法,常被用于加密数据传输.如果配合上数字摘要算法, 也可以用于文件签名. 本文将讨论如何在iOS中使用RSA传输加密数据. 本文环境 mac os openssl-1.0. ...

  3. iOS面向编码|iOSVideoToolbox:读写解码回调函数CVImageBufferRef的YUV图像

    iOS面向编码|iOSVideoToolbox:读写解码回调函数CVImageBufferRef的YUV图像 本文档基于H.264的解码,介绍读写Video Toolbox解码回调函数参数CVImag ...

  4. IOS中使用像素位图(CGImageRef)对图片进行处理

    IOS中对图片进行重绘处理的方法总结 一.CGImageRef是什么 CGImageRef是定义在QuartzCore框架中的一个结构体指针,用C语言编写.在CGImage.h文件中,我们可以看到下面 ...

  5. H.264格式,iOS硬编解码 以及 iOS 11对HEVC硬编解码的支持

    H.264格式,iOS硬编解码 以及 iOS 11对HEVC硬编解码的支持 1,H.264格式 网络表示层NAL,如图H.264流由一帧一帧的NALU组成: SPS:序列参数集,作用于一系列连续的编码 ...

  6. 谈谈 iOS 中图片的解压缩

    原文 对于大多数 iOS 应用来说,图片往往是最占用手机内存的资源之一,同时也是不可或缺的组成部分.将一张图片从磁盘中加载出来,并最终显示到屏幕上,中间其实经过了一系列复杂的处理过程,其中就包括了对图 ...

  7. 【转】谈谈 iOS 中图片的解压缩

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

  8. iOS 中 常用的第三方库

    现在对于我们 iOS 开发来说,基本上说不可能不使用第三方轮子啦,毕竟没那么多时间,而且自己造的轮子往往想着成为上图中的最后一个,结果却成了上图中第二个或第一个啦,当然大公司另当别论.下面我从之前用过 ...

  9. iOS中支付宝集成

    iOS中支付宝集成 如今各种的App中都使用了三方支付的功能,现在将我在使用支付宝支付集成过程的心得分享一下,希望对大家都能有所帮助 要集成一个支付宝支付过程的环境,大致需要: 1>公司:先与支 ...

随机推荐

  1. Android 设置thumb图片大小

    xml: android:thumb="@drawable/seekbar_thumb" seekbar_thumb.xml: <?xml version="1.0 ...

  2. Android ImageButton | Button | TextView 点击和触摸效果

    ImageButton------------------------------------------------> 一.Java  代码: 在drawable目录下添加新的xml文件 bu ...

  3. 编写 Objective-C 代码

    如果您未曾开发过 iOS 或 Mac OS X 平台的程序,那就需要开始了解它们的首要程序设计语言 Objective-C.Objective-C 并不是一种很难的语言,如果能花一点时间学习,相信您会 ...

  4. ruby mysql数据库操作

    require 'mysql' con=Mysql.new('localhost','root','root','test') con.query('set names utf8') rs=con.q ...

  5. BZOJ_1622_[Usaco2008_Open]_Word_Power_名字的能量_(字符匹配_暴力)

    描述 http://www.lydsy.com/JudgeOnline/problem.php?id=1622 给出多个文本串和模式串,求每个文本串中有多少模式串. 分析 直接暴力... #inclu ...

  6. BZOJ_1625_ [Usaco2007_Dec]_宝石手镯_(01背包)

    描述 http://www.lydsy.com/JudgeOnline/problem.php?id=1625 01背包裸题. p.s.随便点开一道就是水题... 分析 ... #include &l ...

  7. ☀【DOM对象 / jQuery对象】

    jQuery对象和DOM对象 √http://www.ituring.com.cn/article/38868 <!DOCTYPE html> <html lang="zh ...

  8. CDN原理

    要了解CDN的实现原理,首先让我们来回顾一下网站传统的访问过程,以便理解其与CDN访问方式之间的差别: 由上图可见,传统的网站访问过程为:1. 用户在浏览器中输入要访问的域名:2. 浏览器向域名解析服 ...

  9. visualbox使用(二)

    1.安装VirtualBox的[增强功能]2.VirtualBox的[设备]->[共享文件夹],添加固定分配,如D:\Java, 名称Java3.执行如下命令#cd /mnt#mkdir w_j ...

  10. HDU2639Bone Collector II(01背包变形)

    01背包,求第k大. 以前看k短路的时候看过代码以为懂了 = =结果还是跑去看了别人的代码才会.果然要自己写一遍才行啊 0.0难得1A.. 每次把可能的2k种求出来,求前k个.注意要不一样的k个数.. ...