Skip to content

Instantly share code, notes, and snippets.

@steipete
Created August 13, 2011 20:52
Show Gist options
  • Select an option

  • Save steipete/1144242 to your computer and use it in GitHub Desktop.

Select an option

Save steipete/1144242 to your computer and use it in GitHub Desktop.
Preload UIImage for super-smooth interaction. especially great if you use JPGs, which otherwise produce a noticeable lag on the main thread.
- (UIImage *)pspdf_preloadedImage {
CGImageRef image = self.CGImage;
// make a bitmap context of a suitable size to draw to, forcing decode
size_t width = CGImageGetWidth(image);
size_t height = CGImageGetHeight(image);
CGColorSpaceRef colourSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef imageContext = CGBitmapContextCreate(NULL, width, height, 8, width*4, colourSpace,
kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little);
CGColorSpaceRelease(colourSpace);
// draw the image to the context, release it
CGContextDrawImage(imageContext, CGRectMake(0, 0, width, height), image);
// now get an image ref from the context
CGImageRef outputImage = CGBitmapContextCreateImage(imageContext);
UIImage *cachedImage = [UIImage imageWithCGImage:outputImage];
// clean up
CGImageRelease(outputImage);
CGContextRelease(imageContext);
return cachedImage;
}
@lbrndnr
Copy link
Copy Markdown

lbrndnr commented Oct 2, 2011

According to this article it's faster to use a JPG than a PNG. Even faster than a crushed one. Do you know how it comes that the PNGs were faster in your tests?

@steipete
Copy link
Copy Markdown
Author

steipete commented Nov 29, 2011 via email

@lbrndnr
Copy link
Copy Markdown

lbrndnr commented Nov 29, 2011

Hmm, I already though about switch to JPG to in my current project. However, some of my resources need an alpha channel. What compression level are you using?

@steipete
Copy link
Copy Markdown
Author

steipete commented Nov 29, 2011 via email

@lbrndnr
Copy link
Copy Markdown

lbrndnr commented Nov 29, 2011

Sweet! I'll look for it after school. Thanks.

@n8r0n
Copy link
Copy Markdown

n8r0n commented Oct 13, 2012

What exactly does this code do? It's not clear to me how it's used. Are you supposed to have already loaded a CGImage? Is this supposed to be called on a background thread? Any sample usage would be much appreciated. Thanks in advance!

@benroo
Copy link
Copy Markdown

benroo commented Mar 15, 2013

Same queries here. And one get the sample code for the background thread? Thanks a lot!

@mbinna
Copy link
Copy Markdown

mbinna commented Apr 17, 2013

Is there a reason to use a bitmap context instead of an image context like the following implementation?

- (UIImage *)pspdf_preloadedImage {
    UIGraphicsBeginImageContextWithOptions(self.size, NO, 0.0f);
    CGRect rect = (CGRect){CGPointZero, self.size};
    [self drawInRect:rect];
    UIImage *preloadedImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return preloadedImage;
}

Using an image context is a little easier to setup. And as far as I understand, there is no need for the low-level control that a bitmap context provides over an image context.

@steipete
Copy link
Copy Markdown
Author

AFAIK, UIGraphicsBeginImageContextWithOptions doesn't set up the bitmap context in the same way as this snipped above (kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little) - so when you then draw the image some post-processing still happens on the main thread. But it's also an implementation detail and might change in future iOS releases.

@mbinna
Copy link
Copy Markdown

mbinna commented Apr 18, 2013

Alright, I see. Thank you for the clarification.

@pj4533
Copy link
Copy Markdown

pj4533 commented May 25, 2013

Just giving a 👍 here....went looking for a fix to my main thread stuttering issue and this solved it. Thanks man!

Using it in my open source project here: https://github.com/pj4533/OpenPics Have a look if you get a second, would love any feedback.

@mohamede1945
Copy link
Copy Markdown

Thank you so much! This snippet is very helpful.

Here is a swift version of it.

extension UIImage {

    func preloadedImage() -> UIImage {

        // make a bitmap context of a suitable size to draw to, forcing decode
        let width = CGImageGetWidth(CGImage)
        let height = CGImageGetHeight(CGImage)

        let colourSpace = CGColorSpaceCreateDeviceRGB()
        let imageContext =  CGBitmapContextCreate(nil,
                                                  width,
                                                  height,
                                                  8,
                                                  width * 4,
                                                  colourSpace,
                                                  CGImageAlphaInfo.PremultipliedFirst.rawValue | CGBitmapInfo.ByteOrder32Little.rawValue)

        // draw the image to the context, release it
        CGContextDrawImage(imageContext, CGRect(x: 0, y: 0, width: width, height: height), CGImage)

        // now get an image ref from the context
        if let outputImage = CGBitmapContextCreateImage(imageContext) {
            let cachedImage = UIImage(CGImage: outputImage)
            return cachedImage
        }

        print("Failed to preload the image")
        return self
    }
}

@rsaunders100
Copy link
Copy Markdown

I think there is a bug where it adds an alpha channel to an image which did not previously have an alpha channel. This slows down rendering slightly I believe.

Could this be fixed by checking the original alpha info with CGImageGetAlphaInfo?

However I am not sure if kCGImageAlphaPremultipliedFirst was picked because it was generic or it was optimal for iOS and using a different alpha info will have other performance implications?

@carlosen14
Copy link
Copy Markdown

carlosen14 commented Aug 26, 2016

Does this generates a smaller version from the original UIImage?

@mickeyl
Copy link
Copy Markdown

mickeyl commented Jan 22, 2017

@rasaunders100: Using kCGImageAlphaNoneSkipFirst instead of kCGImageAlphaPremultipliedFirst gets you an image without alpha.

@steipete: I have seen a similar snippet floating around which does a few additional checks, such as:

        CGImageRef imageRef = self.CGImage;
        
        CGImageAlphaInfo alpha = CGImageGetAlphaInfo( imageRef );
        BOOL anyAlpha = ( alpha == kCGImageAlphaFirst ||
                          alpha == kCGImageAlphaLast ||
                          alpha == kCGImageAlphaPremultipliedFirst ||
                          alpha == kCGImageAlphaPremultipliedLast );
        if ( anyAlpha )
        {
            return self;
        }
        
        size_t width = CGImageGetWidth( imageRef );
        size_t height = CGImageGetHeight( imageRef );
        
        // current
        CGColorSpaceModel imageColorSpaceModel = CGColorSpaceGetModel( CGImageGetColorSpace( imageRef ) );
        CGColorSpaceRef colorspaceRef = CGImageGetColorSpace( imageRef );
        
        bool unsupportedColorSpace = ( imageColorSpaceModel == 0 ||
                                       imageColorSpaceModel == -1 ||
                                       imageColorSpaceModel == kCGColorSpaceModelIndexed );
        if ( unsupportedColorSpace )
        {
            colorspaceRef = CGColorSpaceCreateDeviceRGB();
        }

What do you think about these – are they unnecessary?

@mickeyl
Copy link
Copy Markdown

mickeyl commented Apr 10, 2017

Any idea whether this applies to watchOS as well?

@lastcc
Copy link
Copy Markdown

lastcc commented Jul 20, 2017

ah... why apple don't let us:

let image = UIImage(...)

image.prepareForRenderingAsynchronously = true

@dannofx
Copy link
Copy Markdown

dannofx commented Aug 2, 2017

Thanks for the snippet!
Swift 3 version:

import UIKit

extension UIImage {
    
    func forceLoad() -> UIImage {
        guard let imageRef = self.cgImage else {
            return self //failed
        }
        let width = imageRef.width
        let height = imageRef.height
        let colourSpace = CGColorSpaceCreateDeviceRGB()
        let bitmapInfo: UInt32 = CGImageAlphaInfo.premultipliedFirst.rawValue | CGBitmapInfo.byteOrder32Little.rawValue
        guard let imageContext = CGContext(data: nil, width: width, height: height, bitsPerComponent: 8, bytesPerRow: width * 4, space: colourSpace, bitmapInfo: bitmapInfo) else {
            return self //failed
        }
        let rect = CGRect(x: 0, y: 0, width: width, height: height)
        imageContext.draw(imageRef, in: rect)
        if let outputImage = imageContext.makeImage() {
            let cachedImage = UIImage(cgImage: outputImage)
            return cachedImage
        }
        return self //failed
    }
    

}

@v57
Copy link
Copy Markdown

v57 commented Mar 8, 2018

Pls don't forget to add old image orientation with:
let cachedImage = UIImage(cgImage: outputImage, scale: scale, orientation: imageOrientation)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment