Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does this code decompress a UIImage so much better than the naive approach?

In my app I need to load large JPEG images and display them in a scroll view. In order to keep the UI responsive, I decided to load the images in the background, then display them on the main thread. In order to fully load them in the background, I force each image to be decompressed. I was using this code to decompress an image (note that my app is iOS 7 only, so I understand that using these methods on a background thread is OK):

+ (UIImage *)decompressedImageFromImage:(UIImage *)image {
    UIGraphicsBeginImageContextWithOptions(image.size, YES, 0);
    [image drawAtPoint:CGPointZero];
    UIImage *decompressedImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return decompressedImage;
}

However, I still had long load times, UI stutter, and a lot of memory pressure. I just found another solution:

+ (UIImage *)decodedImageWithImage:(UIImage *)image {
    CGImageRef imageRef = image.CGImage;
    // System only supports RGB, set explicitly and prevent context error
    // if the downloaded image is not the supported format
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();

    CGContextRef context = CGBitmapContextCreate(NULL,
                                                 CGImageGetWidth(imageRef),
                                                 CGImageGetHeight(imageRef),
                                                 8,
                                                 // width * 4 will be enough because are in ARGB format, don't read from the image
                                                 CGImageGetWidth(imageRef) * 4,
                                                 colorSpace,
                                                 // kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little
                                                 // makes system don't need to do extra conversion when displayed.
                                                 kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little);
    CGColorSpaceRelease(colorSpace);

    if ( ! context) {
        return nil;
    }
    CGRect rect = (CGRect){CGPointZero, CGImageGetWidth(imageRef), CGImageGetHeight(imageRef)};
    CGContextDrawImage(context, rect, imageRef);
    CGImageRef decompressedImageRef = CGBitmapContextCreateImage(context);
    CGContextRelease(context);
    UIImage *decompressedImage = [[UIImage alloc] initWithCGImage:decompressedImageRef];
    CGImageRelease(decompressedImageRef);
    return decompressedImage;
}

This code is orders of magnitude better. The image loads almost immediately, there is no UI stutter, and the memory usage has gone way down.

So my question is two-fold:

  1. Why is the second method so much better than the first?
  2. If the second method is better because of the unique parameters of the device, is there a way to ensure that it will work equally well for all iOS devices, present and future? I wouldn't want to assume a native bitmap format that changes on me, reintroducing this problem.
like image 339
Hilton Campbell Avatar asked Oct 30 '13 12:10

Hilton Campbell


People also ask

What is a UIImage?

An object that manages image data in your app.

How do I display an image in Objective C?

UIImageView *imageview = [[UIImageView alloc] initWithFrame:CGRectMake(10.0, 20.0, 0.0, 0.0)]; UIImage *myimg = [UIImage imageNamed:@"A1. jpg"]; imageview. image=myimg; [imageview sizeToFit]; And don't forget to add image view to view hierarchy.

How do I display an image in SwiftUI?

You can display an image in a SwiftUI view by using the Image view. First you need to add the image to a new image set in your Assets. xcassets file in the Xcode project navigator.


1 Answers

I assume that you're running this on a Retina device. In UIGraphicsBeginImageContextWithOptions, you asked for the default scale, which is the scale of the main screen, which is 2. This means that it's generating a bitmap 4x as large. In the second function, you're drawing at 1x scale.

Try passing a scale of 1 to UIGraphicsBeginImageContextWithOptions and see if your performance is similar.

like image 64
Rob Napier Avatar answered Nov 15 '22 20:11

Rob Napier