Декодировать изображения в фоновом потоке?

У меня есть фоновый поток, который загружает изображения и отображает их в основном потоке. Я заметил, что фоновый поток почти ничего не может сделать, потому что фактическое декодирование изображения, по-видимому, выполняется в основном потоке:

alt text

До сих пор Ive пыталась называть [UIImage imageNamed:], [UIImage imageWithData:] и CGImageCreateWithJPEGDataProvider в фоновом потоке без разницы. Есть ли способ заставить декодирование выполняться в фоновом потоке?

Theres уже аналогичный вопрос здесь, но это не помогает. Как я писал там, я попробовал следующий трюк:

@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

Это работает (заставляет декодировать изображение), но также вызывает дорогостоящий вызов ImageIO_BGR_A_TO_RGB_A_8Bit.

Ответ 1

Я столкнулся с подобными проблемами с приветственными изображениями на новой сетчатке iPad. Изображения большего размера, чем размер экрана (примерно), могут вызвать серьезные проблемы с отзывчивом пользовательского интерфейса. Это были JPG, поэтому заставить их декодировать на фоне, казалось, было правильным решением. Я все еще работаю над ужесточением всего этого, но решение Tommy отлично поработало для меня. Я просто хотел внести некоторый код, чтобы помочь следующему человеку, когда они пытаются определить, почему их пользовательский интерфейс заикается большими изображениями. Вот что я сделал (этот код работает в 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);

Я все еще оптимизирую это. Но, похоже, пока это хорошо.

Ответ 2

Официально говоря, UIKit не является потокобезопасным (хотя, как мне кажется, в iOS 4 стало немного безопаснее), поэтому вам не следует называть ни один из методов UIImage в фоновом потоке. На практике я думаю, что вы, как правило, не сталкиваетесь с проблемами, если не делаете ничего, из-за чего объект UIKit хочет перерисовать, но это правило большого пальца, о котором вы никогда не должны опираться на код доставки - я упоминаю его просто для объяснения причин у вас, вероятно, не было проблемы.

Как вы заметили, CGImageRefs (в том числе, когда они завернуты в UIImage) продолжают ссылаться на исходные изображения в их закодированной форме, пока и до тех пор, пока кто-то не захочет их декодировать. "Трюк", который вы цитируете, эффективен для запуска декодирования; лучшее решение - полностью избежать UIImage, пока вы не вернетесь в основной поток, начните с CGImageCreateWithJPEGDataProvider, создайте отдельный контекст, как вы это делаете выше, CGContextDrawImage из файла JPEGDataProvider в новый контекст, а затем передайте новый контекст как изображение, освобождая поставщика данных JPEG.

Что касается ImageIO_BGR_A_TO_RGB_A_8Bit, может оказаться полезным сделать CGColorSpaceCreateDeviceRGB() вместо CGImageGetColorSpace (cgImage), чтобы получить контекст с цветовым пространством, которое не зависит от файла исходного изображения внизу.