iOS 7中的图像解压缩

时间:2014-04-10 16:47:50

标签: ios ios7 uiimage cgimage compression

图像解压缩的问题已在Stack Overflow中进行了大量讨论,但直到这个问题,才提到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方法吗?

3 个答案:

答案 0 :(得分:4)

据我所知,实施方面存在一些问题。

  1. 这个"新方法"需要在主线程上进行某种渲染。您可以加载图像并设置应该缓存立即标志,但这将在主线程中设置一些操作来处理。加载滚动视图和集合视图时,这将导致卡顿。对于我而言,它比在后台使用调度队列执行旧方式更为口吃。

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

    • 使用来自某些来源的压缩JPEG数据填充您自己的缓冲区
    • 创建数据提供者并将JPEG数据附加到其中
    • 使用CACHE IMMEDIATELY设置
    • 的数据提供程序创建图像源
    • 使用图像源创建CG图像
    • 抛弃所有中间对象并将解析好的CGImage对象交给准备滚动的UIImage对象
  3. 虽然它没有这样做,因为它将等待进行解压缩的主线程。它认为一切都很好,因为它引用了你发布的所有这些中间对象。您释放了所有这些对象,认为它会立即解压缩,就像标志所说的那样。如果你丢弃了那个内存缓冲区,并且该内存缓冲区是以无副本的方式传递的,那么你最终会得到垃圾。或者,如果在我的情况下重用内存缓冲区,要加载另一个图像,你也会得到垃圾。

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

    TL; DR ="考虑kCGImageSourceShouldCacheImmediately意味着何时方便操作系统"

    当你按照旧方式进行时,你100%知道什么是可用的以及什么时候可用。因为它没有推迟,你可以避免一些复制。我不认为这个API无论如何都在做任何神奇的事情,我认为它只是拿着内存缓冲区,然后按照旧的方式做事情"引擎盖下。

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

    这意味着kCGImageSourceShouldCacheImmediately nothing 除了设置一个标志,告诉图像在创建它后尽快在主线程中解压缩,而不是实际上立即,因为你认为IMMEDIATELY意味着(在读)。如果您将图像移交到滚动视图以显示并且图像进行绘制,那么它将完成相同的操作。如果你很幸运,在滚动之间会有一些空余的循环,这会改善一些事情,但基本上我认为它听起来更有希望它会比实际做的更多。

答案 1 :(得分:0)

可以这样:

+ (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;
}

答案 2 :(得分:-2)

您可以尝试以下代码:

+(NSData *)imageData:(UIImage *)image
{
    //1.0 == 100%
    return UIImageJPEGRepresentation(image, 0.7);
}

干杯!