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. KeyDown,KeyPress 和KeyUp

    研究了一下KeyDown,KeyPress 和KeyUp ,发现之间还是有点学问的.让我们带着如下问题来说明,如果你看到这些问题你都知道,那么这篇文章你就当复习吧:) 1.这三个事件的顺序是怎么样的? ...

  2. 【原创翻译】Reducing Branch Delay to Zero in Pipelined Processors

    在流水线处理器中减少分支延迟到零 Antonio M. Gonzalez and Jose M. Llaberia 摘要 一种减少流水处理器中分支延迟到零的机制将在本文被描述以及评估.这种机制基于多重 ...

  3. APP-PER-50022: Oracle Human Resources could not retrieve a value for the User Type profile option.

    Symptoms ----------------------- AP > Setup > Organizations Show Error tips: APP-PER-50022: Or ...

  4. 查找Form的文件名

    我们经常会要在ORACLE EBS中寻找我们正在浏览的form页面的执行文件,我们都会直接在Help中的菜单里点击"About Oracle Application",然后查看当前 ...

  5. poj3140Contestants Division

    链接 这叫树形DP吗..?断开某条边 求剩下两颗树数权值和的差最小 dfs一遍 枚举边 查了n久 wa n次  dp数组没初始化.. 在poj上1A感觉应该挺爽 #include <iostre ...

  6. VJP1063 迎春舞会之集体舞(DP)

    我是被这题彻底折腾惨了 .. DP很简单 不用说了 重点是必须按它那个图说来来划分三角形 而不是随便撇下一部分 随便划下一个三角形就可以 所以 要判断J是第奇数个点才可以 #include <i ...

  7. UITableView添加静态背景.

    1:  controller self.view.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@" ...

  8. STL总结之bitset

    STL的bitset是一个对位进行存储和操作的容器,可以轻松对bit位进行访问.   bitset的模板声明如下: template<size_t _Bits> class bitset; ...

  9. FZU 2237 中位数 主席树 树上k大

    #include <cstdio> #include <cstring> #include <queue> #include <set> #includ ...

  10. Codeforces Round #335 (Div. 2)B. Testing Robots解题报告

                                                                                               B. Testin ...