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. 执行gem install dryrun错误

    ERROR:  While executing gem ... (Gem::FilePermissionError)     You don't have write permissions for ...

  2. dojo.hitch 原理

    在使用dojo的时候,遇到dojo.hitch这个函数 ,官方文档说的很清楚,将函数和作用域绑定起来,这让我想起了call和apply这两个函数,call和apply用于改变一个方法的执行上下文,JS ...

  3. GetActiveView 返回 NULL 为 MDI 框架窗口

    blog 在 MDI 应用程序中,MDI 主框架窗口(CMDIFrameWnd) 不具有与其相关联的视图.相反,每个单独的子窗口(CMDIChildWnd)具有与之关联的一个或多个视图.因此,对 MD ...

  4. 【HDOJ】1263 水果

    hash,使用stl map ac.学了find_if等强大的东西,第一次使用stl模板. #include <iostream> #include <cstdio> #inc ...

  5. [转] POJ 题目分类

    转载来自http://www.cnblogs.com/kuangbin/archive/2011/07/29/2120667.html 初期:一.基本算法:     (1)枚举. (poj1753,p ...

  6. -_-#【减少 DOM 元素】textarea, script 延迟渲染

    淘宝详情页的 BigRender 优化与存放大块 HTML 内容的最佳方式 淘宝详情页的BigRender优化的最佳方式 <!DOCTYPE html> <html> < ...

  7. 对easyUI中课堂源码编辑改进建议

    在孙宇老师讲得Easyui第10讲完后,基本的增删该查做出来了,但是编辑存在一个问题:行内样式编辑修改,如果当用户没有修改数据,孙宇老师讲得时候直接return,这样做是不合理的:第二次再使用右键编辑 ...

  8. SharePoint 2013版本功能对比介绍

    转:http://www.fengfly.com/plus/view-213720-1.html 在SharePoint使用中,经常纠结于版本问题,SharePoint 2013主要有免费的Found ...

  9. Ubuntu 12.04 和 Win7 双系统安装

    Thinkpad T400上成功安装双系统 安装Win7 使用光盘按步骤安装,到这里是一个没有分区的硬盘,做了如下分区: 100M(系统保留),40G(C盘),60G(D盘),80G(E盘),52G( ...

  10. NOIP2013 货车运输 LCA倍增+最大生成树

    #include <cstdio> #include <iostream> #include <ctime> #include <vector> #in ...