34

How can you mask a square image into an image with round corners?

Cœur
  • 37,241
  • 25
  • 195
  • 267
erotsppa
  • 14,248
  • 33
  • 123
  • 181

8 Answers8

67

You can use CoreGraphics to create a path for a round rectangle with this code snippet:

static void addRoundedRectToPath(CGContextRef context, CGRect rect, float ovalWidth, float ovalHeight)
{
    float fw, fh;
    if (ovalWidth == 0 || ovalHeight == 0) {
        CGContextAddRect(context, rect);
        return;
    }
    CGContextSaveGState(context);
    CGContextTranslateCTM (context, CGRectGetMinX(rect), CGRectGetMinY(rect));
    CGContextScaleCTM (context, ovalWidth, ovalHeight);
    fw = CGRectGetWidth (rect) / ovalWidth;
    fh = CGRectGetHeight (rect) / ovalHeight;
    CGContextMoveToPoint(context, fw, fh/2);
    CGContextAddArcToPoint(context, fw, fh, fw/2, fh, 1);
    CGContextAddArcToPoint(context, 0, fh, 0, fh/2, 1);
    CGContextAddArcToPoint(context, 0, 0, fw/2, 0, 1);
    CGContextAddArcToPoint(context, fw, 0, fw, fh/2, 1);
    CGContextClosePath(context);
    CGContextRestoreGState(context);
}

And then call CGContextClip(context); to clip it to the rectangle path. Now any drawing done, including drawing an image, will be clipped to the round rectangle shape.

As an example, assuming "image" is a UIImage, and this is in a drawRect: method:

CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
addRoundedRectToPath(context, self.frame, 10, 10);
CGContextClip(context);
[image drawInRect:self.frame];
CGContextRestoreGState(context);
smdvlpr
  • 1,088
  • 9
  • 22
Ecton
  • 10,702
  • 2
  • 35
  • 44
  • That looks very useful, but I'm not familiar with the CG library. If I have a UIImageView/UIImage, where do I go from there? – erotsppa Jun 15 '09 at 14:53
  • You would want to implement the drawRect method on the view with the code I added above. – Ecton Jun 15 '09 at 16:12
  • 5
    Some minor corrections - the drawRect example contains two errors. Call addRoundedRectToPath (not addRoundRectToPath), and to save state call CGContextSaveGState (not CGGraphicsSaveGState). – stpe Aug 13 '09 at 21:18
  • It is easier now. The system provides +[UIBezierPath bezierPathWithRoundedRect:cornerRadius] for this, as I describe below. – algal Dec 13 '12 at 23:23
35

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 * layer = [roundedView layer];
[layer setMasksToBounds:YES];
[layer setCornerRadius:10.0];

// You can even add a border
[layer setBorderWidth:4.0];
[layer setBorderColor:[[UIColor blueColor] CGColor]];

To use these methods you might need to add:

#import <QuartzCore/QuartzCore.h>
Bhawan Bhangu
  • 139
  • 1
  • 5
MagicSeth
  • 4,615
  • 2
  • 27
  • 16
  • 1
    The CoreGraphics Method above posted by NilObject is faster when you are dealing with animating items. Apparently the built in corner radius is not as efficient. – MagicSeth Aug 24 '09 at 01:07
  • 2
    Thanks! If anyone runs into trouble with this, be sure to #import in your implementation file, else none of those methods will fly. – Joe D'Andrea Nov 21 '09 at 02:42
  • 1
    Here you're allocating a UIImageView object. This is not an option when you're dealing with images in UITableViews. – samvermette May 17 '10 at 18:13
  • 1
    it rounds only border - not image itself – RolandasR Nov 03 '11 at 11:22
16

I realize this is old news but just to boil it down a bit:

There are two possible questions here: (1) how do I apply rounded corners to a UIView (such as a UIImageView), which will be displayed on screen, and (2) how do I mask a square image (that is, a UIImage) to produce a new image with rounded corners.

For (1), the easiest course is to use CoreAnimation and set the view.layer.cornerRadius property

 // Because we're using CoreAnimation, we must include QuartzCore.h
 // and link QuartzCore.framework in a build phases 
 #import <QuartzCore/QuartzCore.h> 

 // start with an image 
 UIImage * fooImage = [UIImage imageNamed:@"foo.png"];
 // put it in a UIImageView
 UIView * view = [UIImageView alloc] initWithImage:fooImage];
 // round its corners. This mask now applies to the view's layer's *background*
 view.layer.cornerRadius = 10.f
 // enable masksToBounds, so the mask applies to its foreground, the image
 view.layer.masksToBounds = YES;

For (2), the best way is to use the UIKit graphics operations:

// start with an image 
UIImage * fooImage = [UIImage imageNamed:@"foo.png"];
CGRect imageRect = CGRectMake(0, 0, fooImage.size.width, fooImage.size.height);
// set the implicit graphics context ("canvas") to a bitmap context for images
UIGraphicsBeginImageContextWithOptions(imageRect.size,NO,0.0);
// create a bezier path defining rounded corners
UIBezierPath * path = [UIBezierPath bezierPathWithRoundedRect:imageRect cornerRadius:10.f];
// use this path for clipping in the implicit context
[path addClip];
// draw the image into the implicit context
[fooImage drawInRect:imageRect];
// save the clipped image from the implicit context into an image 
UIImage *maskedImage = UIGraphicsGetImageFromCurrentImageContext();
// cleanup
UIGraphicsEndImageContext();

What's tricky about problem (2) is that you might think you could do the whole operation using the view.layer.mask property in CoreAnimation. But you can't because the CALayer renderInContext: method, which you'd use to generate a UIImage from the masked layer, seems to ignore the mask. Worse, the documentation for renderInContext: doesn't mention this, and only alludes to the behavior for OSX 10.5.

Some further context: the above approach to (2) is using UIKit's wrappers around more basic CoreGraphics functionality. You can do the same thing using the CoreGraphics calls directly – that is what the chosen answer is doing -- but then you need build the rounded rect bezier path manually from curves and lines and you also need to compensate for the fact that CoreGraphics uses a drawing coordinate system which is flipped with respect to UIKit's.

algal
  • 27,584
  • 13
  • 78
  • 80
  • This while working well, mirrors the image horizontally. i removed the CGContextDrawImage((ctx, imageRect, theImage.CGImage); and replaced it with a [theImage drawInRect:CGRectMake(0,0,imageRect.size.width, imageRect.size.height)]; and this resolved the issue. – zachron Dec 14 '12 at 20:45
  • @zachron thanks for the correction. I have re-written this answer to avoid raw CG entirely. That seems recommended based on reading the "Drawing with Quartz and UIKit" section in Apple's http://developer.apple.com/library/ios/#documentation/2DDrawing/Conceptual/DrawingPrintingiOS/GraphicsDrawingOverview/GraphicsDrawingOverview.html – algal Dec 17 '12 at 00:07
2

See this Post - Very simple answer

How to set round corners in UI images in iphone

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];
Community
  • 1
  • 1
GAL
  • 29
  • 1
1

Very simple. self.profileImageView.layer.cornerRadius = self.profileImageView.frame.size.width / 2; self.profileImageView.clipsToBounds = YES;

For every view, there is a bundled layer property. So the first line of the above is to set the corner radius of the layer object (i.e. an instance of CALayer class). To make a circular image from a squared image, the radius is set to the half of the width of UIImageView. For instance, if the width of squared image is 100 pixels. The radius is set to 50 pixels. Secondly, you have to set the clipsToBounds property to YES in order to make the layer works.

Shahab Qureshi
  • 952
  • 1
  • 7
  • 19
0

I use this method.

+ (UIImage *)imageWithColor:(UIColor *)color andSize:(CGSize)size;
    {
      UIImage *img = nil;


        CGRect rect = CGRectMake(0, 0, size.width, size.height);
        UIGraphicsBeginImageContext(rect.size);
        CGContextRef context = UIGraphicsGetCurrentContext();
        CGContextSetFillColorWithColor(context,
                                     color.CGColor);
        CGContextFillRect(context, rect);
        img = UIGraphicsGetImageFromCurrentImageContext();

        UIGraphicsEndImageContext();


      return img;
    }
Voda Ion
  • 3,426
  • 2
  • 16
  • 18
  • 2
    According to memory management rules the image object returned by `UIGraphicsGetImageFromCurrentImageContext` doesn't need to be retained, so you would be leaking memory there. It's the caller of `imageWithColor` which needs to retain it. Maybe you added the retain because of the `NSAutoreelasePool` (which also seems suspicious)? – Grzegorz Adam Hankiewicz Jun 01 '12 at 21:16
0

Building off of algal, here are a couple methods that are nice to put in an UIImage category:


- (UIImage *) roundedCornerImageWithRadius:(CGFloat)radius
{
    CGRect imageRect = CGRectMake(0, 0, self.size.width, self.size.height);
    UIGraphicsBeginImageContextWithOptions(imageRect.size,NO,0.0); //scale 0 yields better results

    //create a bezier path defining rounded corners and use it for clippping
    UIBezierPath * path = [UIBezierPath bezierPathWithRoundedRect:imageRect cornerRadius:radius];
    [path addClip];

    // draw the image into the implicit context
    [self drawInRect:imageRect];

    // get image and cleanup
    UIImage *roundedCornerImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return roundedCornerImage;
}

+ (UIImage *)imageWithColor:(UIColor *)color size:(CGSize)size andCornerRadius:(CGFloat)radius
{
    UIImage *image = nil;
    if (size.width == 0 || size.height == 0) {
        size = CGSizeMake(1.0, 1.0);
    }
    CGRect rect = CGRectMake(0.0f, 0.0f, size.width, size.height);
    UIGraphicsBeginImageContextWithOptions(rect.size,NO,0.0); //yields sharper results than UIGraphicsBeginImageContext(rect.size)
    CGContextRef context = UIGraphicsGetCurrentContext();
    if (context)
    {
        CGContextSetFillColorWithColor(context, [color CGColor]);
        if (radius > 0.0) {
            //create a bezier path defining rounded corners and use it for clippping
            UIBezierPath * path = [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:radius];
            [path addClip];
            CGContextAddPath(context, path.CGPath);
        }
        CGContextFillRect(context, rect);
        image = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
    }
    return image;
}
Community
  • 1
  • 1
n8tr
  • 5,018
  • 2
  • 32
  • 33
0

Both the methods work but the differences shows up depending on where you use it.

For Ex: If you have a table view with the cells showing an image along with other labels, etc., and you use layer to set the cornerRadius, the scrolling will take a big hit. It gets jerky.

I faced this issue when I was using Layer for an image in a table view cell and was trying to figure out the cause of that jerkiness only to find that CALayer was the culprit.

Used the first solution of doing the stuff in drawRect explained by NilObject. That works like a charm with scrolling being smooth as silk.

On the other hand, if you want to use this in static views like popover view, etc., layer is the easiest way to do it.

As I said, both the methods work well just that you need to decide based on where you want to use it.

Deepak G M
  • 1,650
  • 17
  • 12