views:

8080

answers:

11

I'm trying to draw images on the iPhone using with rounded corners, a la the contact images in the Contacts app. I've got code that generally work, but it occasionally crashes inside of the UIImage drawing routines with an EXEC_BAD_ACCESS - KERN_INVALID_ADDRESS. I thought this might be related to the cropping question I asked a few weeks back, but I believe I'm setting up the clipping path correctly.

Here's the code I'm using - when it doesn't crash, the result looks fine and anybody looking to get a similar look is free to borrow the code.

- (UIImage *)borderedImageWithRect: (CGRect)dstRect radius:(CGFloat)radius {
    UIImage *maskedImage = nil;

    radius = MIN(radius, .5 * MIN(CGRectGetWidth(dstRect), CGRectGetHeight(dstRect)));
    CGRect interiorRect = CGRectInset(dstRect, radius, radius);

    UIGraphicsBeginImageContext(dstRect.size);
    CGContextRef maskedContextRef = UIGraphicsGetCurrentContext();
    CGContextSaveGState(maskedContextRef);

    CGMutablePathRef borderPath = CGPathCreateMutable();
    CGPathAddArc(borderPath, NULL, CGRectGetMinX(interiorRect), CGRectGetMinY(interiorRect), radius, PNDegreeToRadian(180), PNDegreeToRadian(270), NO);
    CGPathAddArc(borderPath, NULL, CGRectGetMaxX(interiorRect), CGRectGetMinY(interiorRect), radius, PNDegreeToRadian(270.0), PNDegreeToRadian(360.0), NO);
    CGPathAddArc(borderPath, NULL, CGRectGetMaxX(interiorRect), CGRectGetMaxY(interiorRect), radius, PNDegreeToRadian(0.0), PNDegreeToRadian(90.0), NO);
    CGPathAddArc(borderPath, NULL, CGRectGetMinX(interiorRect), CGRectGetMaxY(interiorRect), radius, PNDegreeToRadian(90.0), PNDegreeToRadian(180.0), NO);

    CGContextBeginPath(maskedContextRef);
    CGContextAddPath(maskedContextRef, borderPath);
    CGContextClosePath(maskedContextRef);
    CGContextClip(maskedContextRef);

    [self drawInRect: dstRect];

    maskedImage = UIGraphicsGetImageFromCurrentImageContext();
    CGContextRestoreGState(maskedContextRef);
    UIGraphicsEndImageContext();

    return maskedImage;
}

and here's the crash log. It looks the same whenever I get one of these crashes

Exception Type:  EXC_BAD_ACCESS (SIGSEGV)
Exception Codes: KERN_INVALID_ADDRESS at 0x6e2e6181
Crashed Thread:  0

Thread 0 Crashed:
0   com.apple.CoreGraphics          0x30fe56d8 CGGStateGetRenderingIntent + 4
1   libRIP.A.dylib                  0x33c4a7d8 ripc_RenderImage + 104
2   libRIP.A.dylib                  0x33c51868 ripc_DrawImage + 3860
3   com.apple.CoreGraphics          0x30fecad4 CGContextDelegateDrawImage + 80
4   com.apple.CoreGraphics          0x30feca40 CGContextDrawImage + 368
5   UIKit                           0x30a6a708 -[UIImage drawInRect:blendMode:alpha:] + 1460
6   UIKit                           0x30a66904 -[UIImage drawInRect:] + 72
7   MyApp                           0x0003f8a8 -[UIImage(PNAdditions) borderedImageWithRect:radius:] (UIImage+PNAdditions.m:187)
+3  A: 

I cant offer any insight into your crash, but I thought I would offer another option for rounding the corners. I had a similar problem arise in an application i was working on. Rather than write any code I am overlaying another image which masks off the corners.

Lounges
I tried masking with an image and was never able to get it to work reliably / at all. Do you have any pointers at how you got it to work?It was a while ago, so I don't exactly remember what I tried, but I think I went through CGImageCreateWithMask, CGImageMaskCreate, and CGContextClipToMask.
Jablair
I am not masking in code. I am simply drawing another UIImage over top of the image i want to mask. The UIImage has a transparent section in the middle and black (my background color) edges that i want to "mask off".
Lounges
+1  A: 

If it only crashes some of the time, figure out what the crash cases have in common. Is dstRect the same every time? Are the images ever a different size?

Also, you need to CGPathRelease(borderPath), although I doubt that leak is causing your problem.

benzado
Thanks for the release reminder. I'd accidentally commented that out with some other debugging code, which I then trimmed before posting.dstRect is the same, but the images could be different because they're downloaded from the web. I'm getting these from a tester - I haven't repro'd this myself.
Jablair
+2  A: 

The easiest way is to embed a disabled[!] round-rect [not custom!] button in your view (can even do it all in the Interface Builder) and associate your image with it. The image-setting message is different for UIButton (compared to UIImageView), but the overall kludge works like a charm. Use setImage:forState: if you want a centered icon or setBackgroundImage:forState: if you want the whole image with corners cut (like Contacts). Of course if you want to display lots of these images in your drawRect this isn't the right approach, but more likely an embedded view is exactly what you needed anyway...

Max Motovilov
+4  A: 

If you are calling your method (borderedImageWithRect) in a background thread, crashes might occur since UIGraphics-functions are not thread-safe. In such a case, you must create a context using CGBitmapContextCreate() - see the "Reflection" sample code from the SDK.

fjoachim
fjoachim, have you actually done this? I'm having a problem drawing to a bitmap context on a background thread. See http://stackoverflow.com/questions/702914/using-core-graphics-cocoa-can-you-draw-to-a-bitmap-context-from-a-background-th
Phil Nash
+1  A: 

I would reiterate fjoachim's answer: be cautious when attempting to draw while running on a separate thread, or you may get EXC_BAD_ACCESS errors.

My workaround went something like this:

UIImage *originalImage = [UIImage imageNamed:@"OriginalImage.png"] 
[self performSelectorOnMainThread:@selector(displayImageWithRoundedCorners:) withObject:originalImage waitUntilDone:YES];

(In my case I was resizing / scaling UIImages.)

PCheese
A: 

I actually had a chance to talk about this with somebody from Apple at the iPhone Tech Talk in New York. When we talked about it, he was pretty sure it wasn't a threading issued. Instead, he thought that I needed to retain the graphics context that was generated when calling UIGraphicsBeginImageContext. This seems to violate the general rules dealing with retain rules and naming schemes, but this fellow was pretty sure he'd seen the issue previously.

If the memory was getting scribbled, perhaps by another thread, that would certainly explain why I was only seeing the issue occasionally.

I haven't had time to revisit the code and test out the fix, but PCheese's comment made me realize I hadn't posted the info here.

...unless I wrote that down wrong and UIGraphicsBeginImageContext should've been CGBitmapContextCreate...

Jablair
A: 

How'd I change the code above to also draw a border around the image? If I just try stroking the border the clipping doesn't occur and stroking doesn't either.

A: 

The CGContextRetain/release help, but just a little

Still, it will crash with less possibility

CGGStateGetRenderingIntent became the remarkable keyword of the problem..

it always crash at this particular process

Since retain doesn't help and it's less merit if resize image on the main thread.

I may try CGBitmapContextCreate()

+28  A: 

Here is an even easier method that is available in iPhone 3.0 and up. Every View-based object has an associated layer. Each layer can have a corner radius set, this will give you just what you want:

UIImageView * roundedView = [[UIImageView alloc] initWithImage: [UIImage imageNamed:@"wood.jpg"]];
// Get the Layer of any view
CALayer * l = [roundedView layer];
[l setMasksToBounds:YES];
[l setCornerRadius:10.0];

// You can even add a border
[l setBorderWidth:4.0];
[l setBorderColor:[[UIColor blueColor] CGColor]];
MagicSeth
Fantastic, thanks for this!
nevan
Obviously, this wasn't the answer back when I asked the question (under 2.0), but it seems to be a great answer under the current world order (ie, 3.x). It's got the added benefit of working with any UIView, which is what brought me back here.
Jablair
As mentioned below, don't forget to include #import <QuartzCore/QuartzCore.h>
Gordon Christie
+3  A: 

If appIconImage is an UIImageView, then:

appIconImage.image = [UIImage imageWithContentsOfFile:@"image.png"]; 
appIconImage.layer.masksToBounds = YES;
appIconImage.layer.cornerRadius = 10.0;
appIconImage.layer.borderWidth = 1.0;
appIconImage.layer.borderColor = [[UIColor grayColor] CGColor];

And also remember:

#import <QuartzCore/QuartzCore.h>
acidscan
A: 

Have you considered using the classes provided here:

http://vocaro.com/trevor/blog/2009/10/12/resize-a-uiimage-the-right-way/

I've found the 3 classes used here to be excellent at providing the functionality you describe.

Urizen