UIImage的圆角处理

60

我正在尝试在iPhone上绘制带有圆角的图像,就像联系人应用程序中的联系人头像一样。我的代码通常可以工作,但有时在UIImage绘图过程中会崩溃,并出现EXEC_BAD_ACCESS - KERN_INVALID_ADDRESS。我认为这可能与我几周前提出的裁剪问题有关,但我相信我已经正确设置了剪切路径。

以下是我正在使用的代码-当它不崩溃时,结果看起来很好,任何想获得类似外观的人都可以借用这段代码。

- (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;
}

以下是崩溃日志。每次出现这种崩溃时,都是相同的:

异常类型:EXC_BAD_ACCESS(SIGSEGV)
异常代码:KERN_INVALID_ADDRESS 0x6e2e6181
崩溃线程:0
线程0崩溃: 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)

为什么要使用CGContextSaveGState()和CGContextRestoreGState()函数?阅读文档,我认为状态是内部的,而整个上下文无论如何都会被丢弃。 - Jaka Jančar
你是否需要像这样调用CGContextBeginPath()和CGContextClosePath()?我发现我只需创建一个路径并调用CGContextAddPath(),一切都可以正常工作。此外,CGContextClosePath()的文档说:“当您填充或剪辑开放路径时,Quartz会隐式地为您关闭子路径”,这表明您不需要自己关闭它。 - camdez
12个回答

164

以下是更简单的方法,适用于 iPhone 3.0 或更新版本。每个基于视图的对象都有一个关联的层(layer)。可以设置每个层的角半径(corner radius),这将为您提供所需效果:

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]];

显然,这并不是我提问时(在2.0版本以下)的答案,但似乎在当前世界秩序下(即3.x)是一个很好的答案。它还有一个额外的好处,就是可以与任何UIView一起使用,这也是我回到这里的原因。 - Jablair
19
如下所述,不要忘记包含 #import <QuartzCore/QuartzCore.h>。 - Gordon Christie
耶!当你有一个UIImageView时,这很棒...但问题是关于UIImage的,不是吗?(如果速度是一个问题,而且你不想分配UIImageView对象。) - Joe D'Andrea
4
提醒遇到同样问题的人注意:在将图片的圆角处理之前,必须先设置图片视图的图片。 - Michael
问题是“UIImage上的圆角”(不是uiimageview),因此最好的答案也应该如此。 - Jonny
哈哈!有趣的是我甚至没有将QuartzCore.framework添加到我的项目中,但在iOS 7上它却像魔法一样运行良好。我是否在其他地方拥有这个框架而不是链接二进制文件库? - Maziyar

29

我将直接回答标题中的问题。

尝试使用这个类别。

UIImage + additions.h

#import <UIKit/UIKit.h>

@interface UIImage (additions)
-(UIImage*)makeRoundCornersWithRadius:(const CGFloat)RADIUS;
@end

UIImage+additions.m

#import "UIImage+additions.h"

@implementation UIImage (additions)
-(UIImage*)makeRoundCornersWithRadius:(const CGFloat)RADIUS {
    UIImage *image = self;

    // Begin a new image that will be the new image with the rounded corners
    // (here with the size of an UIImageView)
    UIGraphicsBeginImageContextWithOptions(image.size, NO, image.scale);

    const CGRect RECT = CGRectMake(0, 0, image.size.width, image.size.height);
    // Add a clip before drawing anything, in the shape of an rounded rect
    [[UIBezierPath bezierPathWithRoundedRect:RECT cornerRadius:RADIUS] addClip];
    // Draw your image
    [image drawInRect:RECT];

    // Get the image, here setting the UIImageView image
    //imageView.image
    UIImage* imageNew = UIGraphicsGetImageFromCurrentImageContext();

    // Lets forget about that we were drawing
    UIGraphicsEndImageContext();

    return imageNew;
}
@end

令我困惑的是,这个问题至今仍然受到如此多的关注。同时,想到当我提出这个问题时(iOS 2.0),我们还没有像UIBezierPath这样的东西,这也让我感到困惑。哎呀,我们已经走了很长的路啊。 - Jablair
@Jablair - 嗯,自从苹果决定在iOS 7中将联系人图片放在圆形视图中,我们现在需要大量的圆形UIImages。 - ArtOfWarfare
如果UIImageView没有使用UIViewContentModeScaleToFill内容模式,那么应该将此标记为正确答案,所有其他答案都是错误的。这适用于任何内容模式。 - SPQR3

22
如果 appIconImage 是一张 UIImageView 图片,那么:
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];

还要记住:

#import <QuartzCore/QuartzCore.h>

4

如果您在后台线程中调用方法(borderedImageWithRect),由于UIGraphics函数不是线程安全的,可能会导致崩溃。在这种情况下,您必须使用CGBitmapContextCreate()创建上下文 - 请参见SDK中的“Reflection”示例代码。


fjoachim,你真的做到了吗?我在后台线程上绘制位图上下文时遇到了问题。请参见https://dev59.com/cEfRa4cB1Zd3GeqP6BEC - philsquared

4

我无法对您的崩溃提供任何见解,但我想为圆角提供另一种选项。在我正在处理的一个应用程序中出现了类似的问题。与其编写任何代码,不如覆盖另一张图像来遮盖圆角。


我曾尝试使用图像进行遮罩,但从未能够可靠地/完全使其正常工作。您有什么提示可以告诉我如何使其正常工作吗?那是一段时间以前的事了,所以我不太记得我尝试了什么,但我想我经历了CGImageCreateWithMask、CGImageMaskCreate和CGContextClipToMask。 - Jablair
我在代码中并没有使用遮罩(masking)。我只是简单地在想要遮罩的图像上面绘制了另一个UIImage。这个UIImage有一个透明的中间部分和黑色(我的背景颜色)的边缘,我想要"遮罩掉"它们。 - Lounges

3
最简单的方法是在你的视图中嵌入一个禁用的圆角矩形按钮(不是自定义按钮!)(甚至可以完全在Interface Builder中完成),并将你的图片与之相关联。对于UIButton,与UIImageView相比,设置图片的消息是不同的,但整体上看起来非常不错。如果你想要一个居中的图标,使用setImage:forState:;如果你想要整个图片带有剪切角(如联系人),则使用setBackgroundImage:forState:。当然,如果你想在drawRect中显示大量这些图片,这不是正确的方法,但更可能是嵌入式视图正是你所需要的...

2
我曾在纽约iPhone技术讲座上与一位苹果公司的人谈论过这个问题。当我们谈论时,他非常确定这不是一个线程问题。相反,他认为我需要保留在调用UIGraphicsBeginImageContext时生成的图形上下文。这似乎违反了保留规则和命名方案的一般规则,但这位先生非常确定他之前见过这个问题。
如果内存被另一个线程篡改,那肯定可以解释为什么我只偶尔会看到这个问题。
我还没有时间重新审查代码并测试修复,但PCheese的评论让我意识到我没有在这里发布信息。
...除非我记错了,UIGraphicsBeginImageContext应该是CGBitmapContextCreate...

2

我想重申fjoachim的答案:在尝试在单独的线程上运行时进行绘制时要小心,否则您可能会收到EXC_BAD_ACCESS错误。

我的解决方法是这样的:

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

(在我的情况下,我正在调整/缩放UIImages。)

1
如果它只在某些情况下崩溃,请找出这些崩溃情况的共同点。 dstRect 是否每次都相同? 图像是否有不同大小?
另外,你需要释放 CGPathRelease(borderPath),尽管我怀疑泄漏会导致你的问题。

感谢您提醒发布事项。在发布前,我不小心将其与其他调试代码一起注释掉了,然后进行了修剪。dstRect是相同的,但图像可能不同,因为它们是从网络上下载的。我是从测试人员那里获取这些信息的 - 我自己还没有复现过这个问题。 - Jablair

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接