UIImage解压缩导致滚动滞后

时间:2012-04-13 22:33:29

标签: iphone ios core-graphics

我有一个全屏幕桌面应用程序,显示一堆微小的图像。这些图像从Web中提取,在后台线程上处理,然后使用以下内容保存到磁盘:

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
        });
    }
});

我的问题是,在第一种情况下,滚动是黄油平滑的。但在第二种情况下(它直接从磁盘读取图像),即使加载图像,滚动也是超级生涩。绘图是造成滞后的因素。使用工具,我看到copyImageBlockSetPNGpng_read_nowinflate占用了大部分cpu(在将self.image分配给UIGraphicsGetImageFromCurrentImageContext()时不是这样)

我假设发生了这种情况,因为在第一种情况下,UIImage是绘图的原始输出,而在第二种情况下,它必须在每次绘制时解压缩PNG。我尝试使用JPG而不是PNG,我得到了类似的结果。

有没有办法解决这个问题?也许只有在它第一次被绘制时才解压缩PNG?

3 个答案:

答案 0 :(得分:23)

您的问题是+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...

答案 1 :(得分:16)

Jason关于预先绘制图像以解压缩它的提示是关键,但通过复制整个图像并丢弃原始图像,您将获得更好的性能。

在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);
        });
    });
}

答案 2 :(得分:6)

图像解压缩的另一种方式:

 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];
  });
}