Подтвердить что ты не робот

Декомпрессия UIImage, вызывающая задержку прокрутки

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

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    UIGraphicsBeginImageContextWithOptions(rect.size, YES, 0);
    // code that adds some glosses, shadows, etc 
    UIImage *output = UIGraphicsGetImageFromCurrentImageContext();

    NSData* cacheData = UIImagePNGRepresentation(output);
    [cacheData writeToFile:thumbPath atomically:YES];

    dispatch_async(dispatch_get_main_queue(), ^{
        self.image = output; // refreshes the cell using KVO
    });
});

Этот код выполняется только при первом отображении ячейки (поскольку после этого изображение уже находится на диске). В этом случае ячейка загружается с использованием:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    UIImage *savedImage = [UIImage imageWithContentsOfFile:thumbPath];

    if(savedImage) {
        dispatch_async(dispatch_get_main_queue(), ^{
            self.image = savedImage; // refreshes the cell using KVO
        });
    }
});

Моя проблема в том, что в первом случае прокрутка является маслом гладким. Но во втором случае (когда он читает изображение непосредственно с диска), прокрутка является очень резкой, даже когда изображение загружается. Рисование - вот что вызывает отставание. Используя инструменты, я вижу, что copyImageBlockSetPNG, png_read_now и inflate занимают большую часть процессора (они не при назначении self.image - UIGraphicsGetImageFromCurrentImageContext())

Я предполагаю, что это происходит, потому что в первом случае UIImage является сырым результатом чертежа, тогда как во втором случае он должен разжимать PNG каждый раз при его рисовании. Я пытался использовать JPG вместо PNG, и получаю аналогичные результаты.

Есть ли способ исправить это? Может быть, чтобы он только распаковывал PNG при первом обращении?

4b9b3361

Ответ 1

Ваша проблема в том, что +imageWithContentsOfFile: кэшируется и ленивая загрузка. Если вы хотите сделать что-то подобное, вместо этого используйте этот код в своей фоновой очереди:

// Assuming ARC
NSData* imageFileData = [[NSData alloc] initWithContentsOfFile:thumbPath];
UIImage* savedImage = [[UIImage alloc] initWithData:imageFileData];

// Dispatch back to main queue and set image...

Теперь, с помощью этого кода, фактическая декомпрессия данных изображения будет по-прежнему лениться и стоить немного, но не так сильно, как доступ к файлу, который вы получаете с ленивой загрузкой в ​​вашем примере кода.

Поскольку вы все еще видите проблему с производительностью, вы также можете заставить UIImage распаковать изображение в фоновом потоке:

// Still on background, before dispatching to main
UIGraphicsBeginImageContext(CGSizeMake(100, 100)); // this isn't that important since you just want UIImage to decompress the image data before switching back to main thread
[savedImage drawAtPoint:CGPointZero];
UIGraphicsEndImageContext();

// dispatch back to main thread...

Ответ 2

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

Изображения, созданные во время выполнения на iOS, как представляется, лучше оптимизированы для рисования, чем те, которые были загружены из файла, даже после того, как вы заставили их распаковывать. По этой причине вы должны загрузиться следующим образом (также неплохо поместить декомпрессированное изображение в NSCache, поэтому вам не нужно перезагружать его):

- (void)loadImageWithPath:(NSString *)path block:(void(^)(UIImage *image))block
{
    static NSCache *cache = nil;
    if (!cache)
    {
        cache = [[NSCache alloc] init];
    }

    //check cache first
    UIImage *image = [cache objectForKey:path];
    if (image)
    {
        block(image);
        return;
    }

    //switch to background thread
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{

        //load image
        UIImage *image = [UIImage imageWithContentsOfFile:path];

        //redraw image using device context
        UIGraphicsBeginImageContextWithOptions(image.size, YES, 0);
        [image drawAtPoint:CGPointZero];
        image = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();

        //back to main thread
        dispatch_async(dispatch_get_main_queue(), ^{

            //cache the image
            [cache setObject:image forKey:path];

            //return the image
            block(image);
        });
    });
}

Ответ 3

Декомпрессия другого способа:

 NS_INLINE void forceImageDecompression(UIImage *image)
 {
  CGImageRef imageRef = [image CGImage];
  CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
  CGContextRef context = CGBitmapContextCreate(NULL, CGImageGetWidth(imageRef), CGImageGetHeight(imageRef), 8, CGImageGetWidth(imageRef) * 4, colorSpace,kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little);
  CGColorSpaceRelease(colorSpace);
  if (!context) { NSLog(@"Could not create context for image decompression"); return; }
  CGContextDrawImage(context, (CGRect){{0.0f, 0.0f}, {CGImageGetWidth(imageRef), CGImageGetHeight(imageRef)}}, imageRef);
  CFRelease(context);
}

Использование:

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
  UIImage *image = [UIImage imageWithContentsOfFile:[NSString stringWithFormat:@"%u.jpg", pageIndex]];
  forceImageDecompression(image);

  dispatch_async(dispatch_get_main_queue(), ^{ 
    [((UIImageView*)page)setImage:image];
  });
}