iOS 7 中的图像解压
Posted
技术标签:
【中文标题】iOS 7 中的图像解压【英文标题】:Image decompression in iOS 7 【发布时间】:2014-04-10 16:47:49 【问题描述】:图像解压缩的问题已经在 Stack Overflow 中进行了很多讨论,但在这个问题之前,有 0 次提到 kCGImageSourceShouldCacheImmediately
,这是 ios 7 中引入的一个选项,理论上可以解决这个问题。从标题:
指定是否应在创建图像时进行图像解码和缓存。
在Objc.io #7 Peter Steinberger 提出了这种方法:
+ (UIImage *)decompressedImageWithData:(NSData *)data
CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
CGImageRef cgImage = CGImageSourceCreateImageAtIndex(source, 0, (__bridge CFDictionaryRef)@(id)kCGImageSourceShouldCacheImmediately: @YES);
UIImage *image = [UIImage imageWithCGImage:cgImage];
CGImageRelease(cgImage);
CFRelease(source);
return image;
AFNetworking 和 SDWebImage 等库仍然使用CGContextDrawImage
方法进行图像解压缩。来自SDWebImage:
+ (UIImage *)decodedImageWithImage:(UIImage *)image
if (image.images)
// Do not decode animated images
return image;
CGImageRef imageRef = image.CGImage;
CGSize imageSize = CGSizeMake(CGImageGetWidth(imageRef), CGImageGetHeight(imageRef));
CGRect imageRect = (CGRect).origin = CGPointZero, .size = imageSize;
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(imageRef);
int infoMask = (bitmapInfo & kCGBitmapAlphaInfoMask);
BOOL anyNonAlpha = (infoMask == kCGImageAlphaNone ||
infoMask == kCGImageAlphaNoneSkipFirst ||
infoMask == kCGImageAlphaNoneSkipLast);
// CGBitmapContextCreate doesn't support kCGImageAlphaNone with RGB.
// https://developer.apple.com/library/mac/#qa/qa1037/_index.html
if (infoMask == kCGImageAlphaNone && CGColorSpaceGetNumberOfComponents(colorSpace) > 1)
// Unset the old alpha info.
bitmapInfo &= ~kCGBitmapAlphaInfoMask;
// Set noneSkipFirst.
bitmapInfo |= kCGImageAlphaNoneSkipFirst;
// Some PNGs tell us they have alpha but only 3 components. Odd.
else if (!anyNonAlpha && CGColorSpaceGetNumberOfComponents(colorSpace) == 3)
// Unset the old alpha info.
bitmapInfo &= ~kCGBitmapAlphaInfoMask;
bitmapInfo |= kCGImageAlphaPremultipliedFirst;
// It calculates the bytes-per-row based on the bitsPerComponent and width arguments.
CGContextRef context = CGBitmapContextCreate(NULL,
imageSize.width,
imageSize.height,
CGImageGetBitsPerComponent(imageRef),
0,
colorSpace,
bitmapInfo);
CGColorSpaceRelease(colorSpace);
// If failed, return undecompressed image
if (!context) return image;
CGContextDrawImage(context, imageRect, imageRef);
CGImageRef decompressedImageRef = CGBitmapContextCreateImage(context);
CGContextRelease(context);
UIImage *decompressedImage = [UIImage imageWithCGImage:decompressedImageRef scale:image.scale orientation:image.imageOrientation];
CGImageRelease(decompressedImageRef);
return decompressedImage;
我的问题是我们是否应该转向 iOS 7 中的 kCGImageSourceShouldCacheImmediately
方法?
【问题讨论】:
免责声明:我是 Haneke 的作者,它是 SDWebImage 的替代品,我要求它告知我如何解码库中的图像。 不清楚的是您遇到的问题是什么导致了这个问题?你为什么担心这个? 【参考方案1】:据我所知,实施存在一些问题。
这种“新方法”需要在主线程上进行某种渲染。您可以加载图像并设置应该立即缓存标志,但这会在主线程中设置一些操作来处理。当您加载滚动视图和集合视图时,这将导致卡顿。它比在后台使用调度队列的旧方法对我来说更卡顿。
如果您使用自己的内存缓冲区而不是文件,则需要创建复制数据的数据提供者,因为看起来数据提供者希望内存缓冲区保留。这听起来很明显,但是这个函数中的标志让你相信你可以做到这一点:
用来自某些来源的压缩 JPEG 数据填充您自己的缓冲区 创建数据提供程序并将 JPEG 数据附加到它 使用设置了缓存的数据提供者创建图像源 使用图像源创建 CG 图像 扔掉所有中间对象,将解压好的 CGImage 对象交给 UIImage 对象,以便滚动但它并没有这样做,因为它会等待将进行解压缩的主线程。它认为一切正常,因为它保存了对您发布的所有这些中间对象的引用。你释放了所有这些对象,认为它会立即解压缩,就像标志所说的那样。如果你也扔掉了那个内存缓冲区,并且那个内存缓冲区是以无拷贝的方式传递的,那么你最终会得到垃圾。或者,如果像我一样重用内存缓冲区来加载另一个图像,你也会得到垃圾。
您实际上无法知道该图像何时会被解压缩并准备好使用。
TL;DR = "考虑 kCGImageSourceShouldCacheImmediately 表示对操作系统方便时"
当您以“旧方式”执行此操作时,您 100% 知道什么时候可用。因为它没有延迟,所以您可以避免一些复制。我认为这个 API 并没有做任何神奇的事情,我认为它只是保持内存缓冲区,然后在引擎盖下以“旧方式”做事。
所以这里基本上没有免费的午餐。查看堆栈跟踪,当我认为内存缓冲区已全部注销后,我开始重用内存缓冲区时,它崩溃了,我看到它调用了 CA::Transaction、CA::Layer、CA::Render 和到 ImageProviderCopy... 一直到 JPEGParseJPEGInfo(它在访问我的缓冲区时崩溃了)。
这意味着 kCGImageSourceShouldCacheImmediately 什么都不做 除了设置一个标志来告诉图像在您创建它后尽快在主线程中解压缩,而不是像您认为的 IMMEDIATELY 的意思(on阅读)。如果您将图像交给滚动视图显示并且图像开始绘制,它会做完全相同的事情。如果你幸运的话,在滚动之间会有一些空闲周期,这会改善事情,但基本上我认为它听起来更有希望,它会做的比实际做的更多。
【讨论】:
【参考方案2】:可以这样:
+ (UIImage *)imageFromURL:(NSURL *)url
UIImage *result = nil;
if ([url isFileURL])
CGImageSourceRef source = CGImageSourceCreateWithURL((__bridge CFURLRef)url, NULL);
if (source)
NSDictionary * attributes = @ (id)kCGImageSourceShouldCache : @YES ;
CGImageRef cgImage = CGImageSourceCreateImageAtIndex(source, 0, (__bridge CFDictionaryRef)attributes);
if (cgImage)
result = [UIImage imageWithCGImage:cgImage];
CGImageRelease(cgImage);
CFRelease(source);
return result;
【讨论】:
【参考方案3】:你可以试试下面的代码:
+(NSData *)imageData:(UIImage *)image
//1.0 == 100%
return UIImageJPEGRepresentation(image, 0.7);
干杯!
【讨论】:
以上是关于iOS 7 中的图像解压的主要内容,如果未能解决你的问题,请参考以下文章
Python遥感图像处理应用篇:Python批量解压Landsat8-tar.gz格式中的指定波段数据