1. 程式人生 > >iOS中的imageIO與image解碼

iOS中的imageIO與image解碼

header images kset final hal 構建 set edev error

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的全家福

技術分享圖片

http://www.cnblogs.com/fengmin/p/5702240.html

iOS中的imageIO與image解碼