在后台线程中解码图像?

zou*_*oul 12 iphone cocoa-touch core-graphics uikit ios

我有一个后台线程加载图像并在主线程中显示它们.我注意到后台线程几乎无事可做,因为实际的图像解码似乎是在主线程中完成的:

替代文字

到目前为止,我已经尝试过调用[UIImage imageNamed:],[UIImage imageWithData:]并且CGImageCreateWithJPEGDataProvider在后台线程中没有任何区别.有没有办法强制解码在后台线程上完成?

这里已经有类似的问题,但它没有帮助.正如我在那里写的,我尝试了以下技巧:

@implementation UIImage (Loading)

- (void) forceLoad
{
    const CGImageRef cgImage = [self CGImage];  

    const int width = CGImageGetWidth(cgImage);
    const int height = CGImageGetHeight(cgImage);

    const CGColorSpaceRef colorspace = CGImageGetColorSpace(cgImage);
    const CGContextRef context = CGBitmapContextCreate(
        NULL, /* Where to store the data. NULL = don’t care */
        width, height, /* width & height */
        8, width * 4, /* bits per component, bytes per row */
        colorspace, kCGImageAlphaNoneSkipFirst);

    NSParameterAssert(context);
    CGContextDrawImage(context, CGRectMake(0, 0, width, height), cgImage);
    CGContextRelease(context);
}

@end
Run Code Online (Sandbox Code Playgroud)

这有效(强制图像解码),但它也会触发一个显然昂贵的调用ImageIO_BGR_A_TO_RGB_A_8Bit.

suj*_*jal 11

我在新的视网膜iPad上遇到了与高分辨率图像类似的问题.大于屏幕尺寸(大致)的图像会导致UI响应性出现重大问题.这些是JPG,所以让它们在背景上解码似乎是正确的做法.我仍在努力收紧所有这些,但Tommy的解决方案对我来说非常有用.我只想提供一些代码来帮助下一个人,当他们试图找出他们的UI为什么用大图像口吃时.这是我最终做的事情(此代码在后台队列的NSOperation中运行).该示例混合了我的代码和上面的代码:

  CGDataProviderRef dataProvider = CGDataProviderCreateWithCFData((CFDataRef)self.data);
  CGImageRef newImage = CGImageCreateWithJPEGDataProvider(dataProvider,
                                    NULL, NO, 
                                    kCGRenderingIntentDefault);


  //////////
  // force DECODE

  const int width = CGImageGetWidth(newImage);
  const int height = CGImageGetHeight(newImage);

  const CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();
  const CGContextRef context = CGBitmapContextCreate(
                                                     NULL, /* Where to store the data. NULL = don’t care */
                                                     width, height, /* width & height */
                                                     8, width * 4, /* bits per component, bytes per row */
                                                     colorspace, kCGImageAlphaNoneSkipFirst);

  NSParameterAssert(context);
  CGContextDrawImage(context, CGRectMake(0, 0, width, height), newImage);
  CGImageRef drawnImage = CGBitmapContextCreateImage(context);
  CGContextRelease(context);
  CGColorSpaceRelease(colorspace);

  //////////

  self.downloadedImage = [UIImage imageWithCGImage:drawnImage];

  CGDataProviderRelease(dataProvider);
  CGImageRelease(newImage);
  CGImageRelease(drawnImage);
Run Code Online (Sandbox Code Playgroud)

我还在优化这个.但到目前为止似乎表现相当不错.


Tom*_*mmy 7

从官方上讲,UIKit不是线程安全的(尽管它在iOS 4中变得有点安全,我相信),所以你不应该在后台线程上调用任何一个UIImage方法.在实践中,我认为如果你不做任何导致UIKit对象想要重绘的事情,你往往不会遇到问题,但这是你不应该依赖于运输代码的经验法则 - 我提到它只是为了解释为什么你可能没有遇到过问题.

正如您所发现的那样,CGImageRefs(包括在UIImage中包装时)继续以编码形式引用源图像,除非并且直到有人需要它们进行解码.你引用的"技巧"可以有效地触发解码; 一个更好的解决方案是完全避免UIImage,直到你安全地回到主线程,从CGImageCreateWithJPEGDataProvider开始,像上面那样创建一个单独的上下文,从JPEGDataProvider到新上下文的CGContextDrawImage,然后传递新的上下文作为图像,释放JPEG数据提供者.

对于ImageIO_BGR_A_TO_RGB_A_8Bit,可能值得给CGColorSpaceCreateDeviceRGB()代替CGImageGetColorSpace(cgImage),以获得具有不依赖于下面的源图像文件的颜色空间的上下文.