UIImage decompression causing scrolling lag

时光怂恿深爱的人放手 提交于 2019-12-03 02:44:37

问题


I have this app with a full screen tableView that displays a bunch of tiny images. Those images are pulled from the web, processed on a background thread, and then saved to disk using something like:

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

This code is only executed the first time the cell is displayed (since after that the image is already on disk). In that case, the cell is loaded using:

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

My problem is that in the first case, scrolling is butter smooth. But in the 2nd case (where it's reading the image directly from disk), scrolling is super jerky, even once the image is loaded. Drawing is what's causing the lag. Using Instruments, I see copyImageBlockSetPNG, png_read_now and inflate are taking up most of the cpu (they aren't when assigning self.image to UIGraphicsGetImageFromCurrentImageContext())

I'm assuming this happens because in the first case the UIImage is a raw output of the drawing, whereas in the second case it has to decompress the PNG every time it's drawing it. I tried using JPGs instead of PNGs and I get similar results.

Is there a way to fix this? Maybe to have it only decompress the PNG the first time it gets drawn?


回答1:


Your problem is that +imageWithContentsOfFile: is cached and lazy loading. If you want to do something like this, instead use this code on your background queue:

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

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

Now, with this code, the actual decompression of the image data will still be lazy and cost a little bit, but not nearly as much as the file access hit you're getting with the lazy loading in your code example.

Since you're still seeing a performance issue, you can also force UIImage to decompress the image on the background thread:

// 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:


Jason's tip about pre-drawing the image to decompress it is the key, but you'll get even better performance by copying the whole image and discarding the original.

Images created at runtime on iOS seem to be better optimised for drawing than ones that have been loaded from a file, even after you've forced them to decompress. For that reason, you should load like this (it's also a good idea to put the decompressed image into an NSCache so you don't have to keep reloading it):

- (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:


Another way image decompression:

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

Using:

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


来源:https://stackoverflow.com/questions/10149165/uiimage-decompression-causing-scrolling-lag

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!