CGContextDrawImage is EXTREMELY slow after large UIImage drawn into it

It seems that CGContextDrawImage(CGContextRef, CGRect, CGImageRef) performs MUCH WORSE when drawing a CGImage that was created by CoreGraphics (i.e. with CGBitmapContextCreateImage) than it does when drawing the CGImage which backs a UIImage. See this testing method:

-(void)showStrangePerformanceOfCGContextDrawImage
{
    ///Setup : Load an image and start a context:
    UIImage *theImage = [UIImage imageNamed:@"reallyBigImage.png"];
    UIGraphicsBeginImageContext(theImage.size);
    CGContextRef ctxt = UIGraphicsGetCurrentContext();    
    CGRect imgRec = CGRectMake(0, 0, theImage.size.width, theImage.size.height);


    ///Why is this SO MUCH faster...
    NSDate * startingTimeForUIImageDrawing = [NSDate date];
    CGContextDrawImage(ctxt, imgRec, theImage.CGImage);  //Draw existing image into context Using the UIImage backing    
    NSLog(@"Time was %f", [[NSDate date] timeIntervalSinceDate:startingTimeForUIImageDrawing]);

    /// Create a new image from the context to use this time in CGContextDrawImage:
    CGImageRef theImageConverted = CGBitmapContextCreateImage(ctxt);

    ///This is WAY slower but why??  Using a pure CGImageRef (ass opposed to one behind a UIImage) seems like it should be faster but AT LEAST it should be the same speed!?
    NSDate * startingTimeForNakedGImageDrawing = [NSDate date];
    CGContextDrawImage(ctxt, imgRec, theImageConverted);
    NSLog(@"Time was %f", [[NSDate date] timeIntervalSinceDate:startingTimeForNakedGImageDrawing]);


}

So I guess the question is, #1 what may be causing this and #2 is there a way around it, i.e. other ways to create a CGImageRef which may be faster? I realize I could convert everything to UIImages first but that is such an ugly solution. I already have the CGContextRef sitting there.

  • Drawing with Core Graphics looks chunky on Retina display
  • How to draw a UILabel with a different blend mode in draw(_ rect: CGRect) in Swift
  • CGBitmapContextCreate: unsupported parameter combination. How to pass kCGImageAlphaNoneSkipFirst
  • Draw triangle cutout for selection indication in UIView
  • Adding a circle mask layer on an UIImageView
  • iOS: CGAffineTransformScale moves my object
  • UPDATE : This seems to not necessarily be true when drawing small images? That may be a clue- that this problem is amplified when large images (i.e. fullsize camera pics) are used. 640×480 seems to be pretty similar in terms of execution time with either method

    UPDATE 2 : Ok, so I’ve discovered something new.. Its actually NOT the backing of the CGImage that is changing the performance. I can flip-flop the order of the 2 steps and make the UIImage method behave slowly, whereas the “naked” CGImage will be super fast. It seems whichever you perform second will suffer from terrible performance. This seems to be the case UNLESS I free memory by calling CGImageRelease on the image I created with CGBitmapContextCreateImage. Then the UIImage backed method will be fast subsequently. The inverse it not true. What gives? “Crowded” memory shouldn’t affect performance like this, should it?

    UPDATE 3 : Spoke too soon. The previous update holds true for images at size 2048×2048 but stepping up to 1936×2592 (camera size) the naked CGImage method is still way slower, regardless of order of operations or memory situation. Maybe there are some CG internal limits that make a 16MB image efficient whereas the 21MB image can’t be handled efficiently. Its literally 20 times slower to draw the camera size than a 2048×2048. Somehow UIImage provides its CGImage data much faster than a pure CGImage object does. o.O

    UPDATE 4 : I thought this might have to do with some memory caching thing, but the results are the same whether the UIImage is loaded with the non-caching [UIImage imageWithContentsOfFile] as if [UIImage imageNamed] is used.

    UPDATE 5 (Day 2) : After creating mroe questions than were answered yesterday I have something solid today. What I can say for sure is the following:

    • The CGImages behind a UIImage don’t use alpha. (kCGImageAlphaNoneSkipLast). I thought that maybe they were faster to be drawn because my context WAS using alpha. So I changed the context to use kCGImageAlphaNoneSkipLast. This makes the drawing MUCH faster, UNLESS:
    • Drawing into a CGContextRef with a UIImage FIRST, makes ALL subsequent image drawing slow

    I proved this by 1)first creating a non-alpha context (1936×2592). 2) Filled it with randomly colored 2×2 squares. 3) Full frame drawing a CGImage into that context was FAST (.17 seconds) 4) Repeated experiment but filled context with a drawn CGImage backing a UIImage. Subsequent full frame image drawing was 6+ seconds. SLOWWWWW.

    Somehow drawing into a context with a (Large) UIImage drastically slows all subsequent drawing into that context.

    2 Solutions Collect From Internet About “CGContextDrawImage is EXTREMELY slow after large UIImage drawn into it”

    Well after a TON of experimentation I think I have found the fastest way to handle situations like this. The drawing operation above which was taking 6+ seconds now .1 seconds. YES. Here’s what I discovered:

    • Homogenize your contexts & images with a pixel format! The root of the question I asked boiled down to the fact that the CGImages inside a UIImage were using THE SAME PIXEL FORMAT as my context. Therefore fast. The CGImages were a different format and therefore slow. Inspect your images with CGImageGetAlphaInfo to see which pixel format they use. I’m using kCGImageAlphaNoneSkipLast EVERYWHERE now as I don’t need to work with alpha. If you don’t use the same pixel format everywhere, when drawing an image into a context Quartz will be forced to perform expensive pixel-conversions for EACH pixel. = SLOW

    • USE CGLayers! These make offscreen-drawing performance much better. How this works is basically as follows. 1) create a CGLayer from the context using CGLayerCreateWithContext. 2) do any drawing/setting of drawing properties on THIS LAYER’s CONTEXT which is gotten with CGLayerGetContext. READ any pixels or information from the ORIGINAL context. 3) When done, “stamp” this CGLayer back onto the original context using CGContextDrawLayerAtPoint.This is FAST as long as you keep in mind:

    • 1) Release any CGImages created from a context (i.e. those created with CGBitmapContextCreateImage) BEFORE “stamping” your layer back into the CGContextRef using CGContextDrawLayerAtPoint. This creates a 3-4x speed increase when drawing that layer. 2) Keep your pixel format the same everywhere!! 3) Clean up CG objects AS SOON as you can. Things hanging around in memory seem to create strange situations of slowdown, probably because there are callbacks or checks associated with these strong references. Just a guess, but I can say that CLEANING UP MEMORY ASAP helps performance immensely.

    I had a similar problem. My application has to redraw a picture almost as large as the screen size. The problem came down to drawing as fast as possible two images of the same resolution, neither rotated nor flipped, but scaled and positioned in different places of the screen each time. After all, I was able to get ~15-20 FPS on iPad 1 and ~20-25 FPS on iPad4. So… hope this helps someone:

    1. Exactly as typewriter said, you have to use the same pixel format. Using one with AlphaNone gives a speed boost. But even more important, argb32_image call in my case did numerous calls converting pixels from ARGB to BGRA. So the best bitmapInfo value for me was (at the time; there is a probability that Apple can change something here in the future):
      const CGBitmabInfo g_bitmapInfo = kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipLast;
    2. CGContextDrawImage may work faster if rectangle argument was made integral (by CGRectIntegral). Seems to have more effect when image is scaled by factor close to 1.
    3. Using layers actually slowed down things for me. Probably something was changed since 2011 in some internal calls.
    4. Setting interpolation quality for the context lower than default (by CGContextSetInterpolationQuality) is important. I would recommend using (IS_RETINA_DISPLAY ? kCGInterpolationNone : kCGInterpolationLow). Macros IS_RETINA_DISPLAY is taken from here.
    5. Make sure you get CGColorSpaceRef from CGColorSpaceCreateDeviceRGB() or the like when creating context. Some performance issues were reported for getting fixed color space instead of requesting that of the device.
    6. Inheriting view class from UIImageView and simply setting self.image to the image created from context proved useful to me. However, read about using UIImageView first if you want to do this, for it requires some changes in code logic (because drawRect: isn’t called anymore).
    7. And if you can avoid scaling your image at the time of actual drawing, try to do so. Drawing non-scaled image is significantly faster – unfortunately, for me that was not an option.