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 中的图像解压的主要内容,如果未能解决你的问题,请参考以下文章

ios 计算图片解压为位图后大小

如何解压pkl文件?

Python遥感图像处理应用篇:Python批量解压Landsat8-tar.gz格式中的指定波段数据

Python遥感图像处理应用篇:Python批量解压Landsat8-tar.gz格式中的指定波段数据

文件格式(图像 IO 14.3)

软件测试---文件压缩解压项目