views:

68

answers:

3

[update: this problem has been resolved; the issue was not in drawInRect: but in UIGraphicsBeginImageContext()]

In my app, I'm grabbing a bunch of large images, cropping them down to thumbnail size and storing the thumbnails for previewing.

Note that I'm doing this in a separate image context -- this is not about redrawing a UIView that is on the screen.

This code is rather intensive so I'm running it in a separate thread. The actual scaling looks like this, and is a category implementation on top of UIImage:

- (UIImage *) scaledImageWithWidth:(CGFloat)width andHeight:(CGFloat)height
{
    CGRect rect = CGRectMake(0.0, 0.0, width, height);
    UIGraphicsBeginImageContext(rect.size);
    [self drawInRect:rect]; // <-- crashing on this line
    UIImage *scaledImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return scaledImage;
}

This is called from a separate method, which loops through the images in turn and does the processing. The actual call to the above method looks like this:

UIImage *small = [bigger scaledImageWithWidth:24.f andHeight:32.f];

This all works most of the time, but occasionally I get an EXC_BAD_ACCESS.

Backtrace:

#0  0x330d678c in ripc_RenderImage ()
#1  0x330dd5aa in ripc_DrawImage ()
#2  0x300e3276 in CGContextDelegateDrawImage ()
#3  0x300e321a in CGContextDrawImage ()
#4  0x315164c8 in -[UIImage drawInRect:blendMode:alpha:] ()
#5  0x31516098 in -[UIImage drawInRect:] ()
#6  0x0000d6e4 in -[UIImage(Scaling) scaledImageWithWidth:andHeight:] (self=0x169320, _cmd=0x30e6e, width=48, height=64) at /Users/me/Documents/svn/app/trunk/Classes/UIImage+Scaling.m:20
#7  0x00027df0 in -[mgMinimap loadThumbnails] (self=0x13df00, _cmd=0x30d05) at /Users/me/Documents/svn/app/trunk/Classes/mgMinimap.m:167
#8  0x32b15bd0 in -[NSThread main] ()
#9  0x32b81cfe in __NSThread__main__ ()
#10 0x30c8f78c in _pthread_start ()
#11 0x30c85078 in thread_start ()

[update 4] When I run this in the Simulator, and this problem happens, the console additionally shows the following:

// the below is before loading the first thumbnail
<Error>: CGContextSaveGState: invalid context
<Error>: CGContextSetBlendMode: invalid context
<Error>: CGContextSetAlpha: invalid context
<Error>: CGContextTranslateCTM: invalid context
<Error>: CGContextScaleCTM: invalid context
<Error>: CGContextDrawImage: invalid context
<Error>: CGContextRestoreGState: invalid context
<Error>: CGBitmapContextCreateImage: invalid context
// here, the first thumbnail has finished loading and the second one
// is about to be generated
<Error>: CGContextSetStrokeColorWithColor: invalid context
<Error>: CGContextSetFillColorWithColor: invalid context

My gut feeling is that I occasionally end up trying to drawInRect: while the OS is also trying to draw something, which results, occasionally, in a crash. I always presumed that as long as you don't draw on the actual screen, this is acceptable -- is this not the case? Or if it is the case, any idea what might be causing this?

Update (r2): I forgot to mention that this app is running under rather severe memory constraints (I've got a lot of images loaded at any given time and these are swapped in/out), so this may be a case of running out of memory (read on -- it's not). I'm not sure how to verify that, though, so thoughts on this would be welcome too. I did verify this by severely cutting down on the number of images being loaded and adding a check to make sure they're properly deallocated (they are, and the crash still occurs).

Update 3: I thought I found the problem. Below is the answer I wrote, before the crash happened again:

The code would (after I posted this question) occasionally start exiting with exit code 0, and sometimes with exit code 10 (SIGBUS). 0 means "no error", so that was extremely odd. 10 seems to mean a bit of everything so that was unhelpful too. The drawInRect: call was a big hint, though, when the crash happened there.

The looping through to get the thumbnails was generating a lot of autoreleased images. I had an autorelease pool but it was wrapping the entire for loop. I added a second autorelease pool within the for loop:

- (void)loadThumbnails
{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    for (...) {
        NSAutoreleasePool *cyclePool = 
           [[NSAutoreleasePool alloc] init]; // <-- here
        UIImage *bigger = ...;
        UIImage *small = [bigger scaledImageWithWidth:24.f andHeight:32.f];
        UIImage *bloated = [i scaledImageWithWidth:48.f andHeight:64.f];
        [cyclePool release]; // <-- ending here
    }
    [pool release];
}

I thought the above fixed the issue, until I ran the app and it crashed on me with "exit code 0" again just earlier. Back to the drawing board...

A: 

I have a feeling you shouldn't be calling drawInRect directly as it may not be thread safe. You should be calling setNeedsDisplay on the view which will send a message to drawInRect when it is safe to do so.

davbryn
If you look at the code, you'll note that this is impossible as I'm drawing the rect in a separate image context.
Kalle
From watching the Stanford iTunes U tutorials, which was given by Apple employees, they explicitly stated that you never call drawRect directly. I don't know if the same applies to drawInRect though.
Joost Schuur
It does not. There are about 5 Apple examples which do this call. One example is here (from cook book example code): http://developer.apple.com/iphone/library/samplecode/iPhoneCoreDataRecipes/Listings/Classes_RecipeDetailViewController_m.html
Kalle
A: 

Have you looked at Matt Gemmell's latest release, MGImageUtilities? I extracted this from his source on github:

// Create appropriately modified image.
UIImage *image;
UIGraphicsBeginImageContextWithOptions(destRect.size, NO, 0.0); // 0.0 for scale means "correct scale for device's main screen".
CGImageRef sourceImg = CGImageCreateWithImageInRect([self CGImage], sourceRect); // cropping happens here.
image = [UIImage imageWithCGImage:sourceImg scale:0.0 orientation:self.imageOrientation]; // create cropped UIImage.
[image drawInRect:destRect]; // the actual scaling happens here, and orientation is taken care of automatically.
CGImageRelease(sourceImg);
image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

Not sure if thread safety is the issue, but it may be worth trying Matt's code before going too far down that path.

Matt Long
Interesting. Will look. Thanks for the tip!
Kalle
I looked into this closer, and even tried doing it the same way (I already sort of was, though), but no luck. Same call (drawInRect:), same error.
Kalle
A: 

It turns out, there are two answers:

"Must drawInRect: for a separate context be executed on the main thread?" The answer is no, it doesn't.

However, UIGraphicsBeginImageContext must. This in fact is the reason for the crashing that occured. The crash didn't reveal itself until the (invalid) graphics context was being altered, which is why the crash occured on the drawInRect: line.

The solution was to stop using UIGraphicsContext and instead use CGBitmapContext, which is thread safe.

Kalle