ios - iOS 7 中的图像解压缩

标签 ios ios7 uiimage cgimage compression

图像解压缩的问题在 Stack Overflow 中已经被广泛讨论,但是直到这个问题有 0 次提及 kCGImageSourceShouldCacheImmediately,这是 iOS 7 中引入的一个选项,理论上可以解决这个问题.来自标题:

Specifies whether image decoding and caching should happen at image creation time.

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 方法?

最佳答案

据我所知,实现存在一些问题。

  1. 这个“新方法”需要在主线程上进行某种渲染。您可以加载图像并设置 should cache immediately 标志,但这会在主线程中设置一些操作来处理。当您加载 ScrollView 和 Collection View 时,这将导致卡顿。与在后台使用调度队列的旧方法相比,它对我来说更加卡顿。

  2. 如果您使用自己的内存缓冲区而不是文件,则需要创建复制数据的数据提供程序,因为看起来数据提供程序希望内存缓冲区保持不变。这听起来很明显,但此函数中的标志会让您相信您可以做到这一点:

    • 用来自某些来源的压缩 JPEG 数据填充您自己的缓冲区
    • 创建数据提供者并将 JPEG 数据附加到它
    • 使用设置了 CACHE IMMEDIATELY 的数据提供者创建图像源
    • 使用图像源创建CG图像
    • 扔掉所有中间对象,将解压缩后的 CGImage 对象交给准备好滚动的 UIImage 对象

但它并没有这样做,因为它将等待解压发生的主线程。它认为一切正常,因为它持有对您发布的所有这些中间对象的引用。您释放了所有这些对象,认为它会像标志所说的那样立即解压缩。如果您也丢弃了该内存缓冲区,并且该内存缓冲区是在非复制意义上传递的,那么您将以垃圾告终。或者,如果内存缓冲区像我的情况一样被重用,以加载另一个图像,您也会得到垃圾。

您实际上无法知道此图像何时会被解压缩并准备好使用。

TL;DR = "将 kCGImageSourceShouldCacheImmediately 视为操作系统方便时的意思"

当您以“旧方式”执行此操作时,您 100% 知道什么时候可用。因为它没有延迟,所以您可以避免一些复制。我不认为这个 API 有任何神奇的作用,我认为它只是持有内存缓冲区,然后在后台以“老方法”做事。

所以这里基本上没有免费的午餐。当我认为它已全部注销后我开始重新使用我的内存缓冲区时,从这个东西崩溃的地方查看堆栈跟踪,我看到它调用了 CA::Transaction、CA::Layer、CA::Render,并从进入 ImageProviderCopy...一直到 JPEGParseJPEGInfo(访问我的缓冲区时崩溃)。

这意味着 kCGImageSourceShouldCacheImmediately 除了设置一个标志告诉图像在创建后尽快在主线程中解压缩之外什么都不做,而不是像你认为的那样立即解压阅读)。如果您将图像交给 ScrollView 进行显示并且图像开始绘制,它会做完全相同的事情。如果幸运的话,滚动之间有一些空闲周期,这会改善情况,但基本上我认为这听起来更有希望,它会比实际做的更多。

关于ios - iOS 7 中的图像解压缩,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22994153/

相关文章:

ios - 构建 iOS 网络应用程序(REST 客户端)的最佳架构方法

ios - iOS 7 Web 应用程序中的 HTTP 身份验证没有响应

ios - 如何使用 imageForState 获取按钮的当前图像?

ios - 更改 UIImage 中的白色像素

ios - 直接链接到 iOS 7 中的应用商店应用程序

ios - 带边框和阴影的圆形按钮

ios - 如何在ios中的谷歌地图中添加倾斜效果?

ios - 仅针对 Crashlytics 从 Fabric 迁移到 Firebase 时需要减小应用程序大小的建议

ios - 保存 UIImage 的缩略图会横向/上下翻转图像

ios - 让 RestKit 留下特定的孤儿 CoreData 托管对象