26

I've got a UIImageView (full frame and rectangular) that i'm rotating with a CGAffineTransform. The UIImage of the UIImageView fills the entire frame. When the image is rotated and drawn the edges appear noticeably jagged. Is there anything I can do to make it look better? It's clearly not being anti-aliased with the background.

Peter Hosey
  • 95,783
  • 15
  • 211
  • 370
Meltemi
  • 37,979
  • 50
  • 195
  • 293

8 Answers8

55

The edges of CoreAnimation layers aren't antialiased by default on iOS. However, there is a key that you can set in Info.plist that enables antialiasing of the edges: UIViewEdgeAntialiasing.

https://developer.apple.com/library/content/documentation/General/Reference/InfoPlistKeyReference/Articles/iPhoneOSKeys.html

If you don't want the performance overhead of enabling this option, a work-around is to add a 1px transparent border around the edge of the image. This means that the 'edges' of the image are no longer on the edge, so don't need special treatment!

Cœur
  • 37,241
  • 25
  • 195
  • 267
Johan Kool
  • 15,637
  • 8
  • 64
  • 81
28

New API – iOS 6/7

Also works for iOS 6, as noted by @Chris, but wasn't made public until iOS 7.

Since iOS 7, CALayer has a new property allowsEdgeAntialiasing which does exactly what you want in this case, without incurring the overhead of enabling it for all views in your application! This is a property of CALayer, so to enable this for a UIView you use myView.layer.allowsEdgeAntialiasing = YES.

Patrick Pijnappel
  • 7,317
  • 3
  • 39
  • 39
17

just add 1px transparent border to your image

CGRect imageRect = CGRectMake(0, 0, image.size.width, image.size.height);
UIGraphicsBeginImageContextWithOptions(imageRect.size, NO, 0.0);
[image drawInRect:CGRectMake(1,1,image.size.width-2,image.size.height-2)];
image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
raidfive
  • 6,603
  • 1
  • 35
  • 32
Onnmir
  • 1,030
  • 14
  • 17
10

Remember to set the appropriate anti-alias options:

CGContextSetAllowsAntialiasing(theContext, true);
CGContextSetShouldAntialias(theContext, true);
Nathan de Vries
  • 15,481
  • 4
  • 49
  • 55
  • I'm not seeing any difference when adding these Antialiasing lines to enclosing the UIImageView's drawRect...as well as the enclosing superview's. Even tried: CGContextSetInterpolationQuality(context, kCGInterpolationHigh); to no avail... When the UIImageView rect gets transformed (rotated) the edges are always jaggy.. – Meltemi Nov 04 '09 at 09:03
  • 5
    Best way to solve that problem is by adding a 1px transparent border to your image prior to rotation. – Nathan de Vries Nov 04 '09 at 11:48
  • How would you add the transparent border? I'm have the same issue while rotating a CALayer with perspective. – Brian Apr 23 '10 at 18:01
  • 2
    Could someone provide a tip on this 1px transparent border trick? I've set a UIView's layer.borderColor and layer.borderWidth, but it does not fix it for me. – akaru Feb 08 '11 at 06:07
  • My image was coming via glReadPixels (OpenGL ES frame buffer capture) so I was able to set alpha on the RGBA memory buffer directly. For those also in this situation make sure you don't use premultiplied in your CGImageCreate call (ie I supplied kCGImageAlphaLast | kCGBitmapByteOrder32Big for CGBitmapInfo). – Quintin Willison Jul 21 '11 at 15:36
  • Explanation why the 1px transparent pixel fixes the problem: the jagged line is the edge of the polygon that Cocoa draws there and since your texture has colors to the edge it can't be anti-aliased. The image inside the edges is anti-aliased though. – Dimitris Sep 15 '11 at 14:33
  • This is besides the problem. The problem is in CALayer space, and not CGContext drawing space. – monkeydom May 16 '12 at 09:42
  • Okay, and how to use it. Or is it for drawing code only? – Vyachaslav Gerchicov Jul 28 '15 at 12:17
6

just add "Renders with edge antialiasing" with YES in plist and it will work.

Sanoj Kashyap
  • 5,020
  • 4
  • 49
  • 75
5

I would totally recommend the following library.

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

It contains lots of useful extensions to UIImage that solve this problem and also include code for generating thumbnails etc.

Enjoy!

Sway
  • 1,647
  • 3
  • 16
  • 19
2

The best way I've found to have smooth edges and a sharp image is to do this:

CGRect imageRect = CGRectMake(0, 0, self.photo.image.size.width, self.photo.image.size.height);
UIGraphicsBeginImageContextWithOptions(imageRect.size, NO, 0.0);
[self.photo.image drawInRect:CGRectMake(1, 1, self.photo.image.size.width - 2, self.photo.image.size.height - 2)];
self.photo.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

Adding the Info.plist key like some people describe has a big hit on performance and if you use that then you're basically applying it to everything instead of just the one place you need it.

Also, don't just use UIGraphicsBeginImageContext(imageRect.size); otherwise the layer will be blurry. You have to use UIGraphicsBeginImageContextWithOptions like I've shown.

iwasrobbed
  • 46,496
  • 21
  • 150
  • 195
  • 1
    This is a great point regarding **UIGraphicsBeginImageContextWithOptions** .. usually you have to set the **opaque option to NO**. I was drawing lines (ie, lines on a transparent bg), and then using renderInContext to draw the image of the lines, into, an existing photo. **Of course, it's critical to use opaque "NO".** So, this was very obvious to me - after iWasRobbed told me about it :) Thanks, robbed! :) – Fattie Dec 18 '13 at 10:21
0

I found this solution from here, and it's perfect:

+ (UIImage *)renderImageFromView:(UIView *)view withRect:(CGRect)frame transparentInsets:(UIEdgeInsets)insets {
    CGSize imageSizeWithBorder = CGSizeMake(frame.size.width + insets.left + insets.right, frame.size.height + insets.top + insets.bottom);
    // Create a new context of the desired size to render the image
    UIGraphicsBeginImageContextWithOptions(imageSizeWithBorder, NO, 0);
    CGContextRef context = UIGraphicsGetCurrentContext();

    // Clip the context to the portion of the view we will draw
    CGContextClipToRect(context, (CGRect){{insets.left, insets.top}, frame.size});
    // Translate it, to the desired position
    CGContextTranslateCTM(context, -frame.origin.x + insets.left, -frame.origin.y + insets.top);

    // Render the view as image
    [view.layer renderInContext:UIGraphicsGetCurrentContext()];

    // Fetch the image
    UIImage *renderedImage = UIGraphicsGetImageFromCurrentImageContext();

    // Cleanup
    UIGraphicsEndImageContext();

    return renderedImage;
}

usage:

UIImage *image = [UIImage renderImageFromView:view withRect:view.bounds transparentInsets:UIEdgeInsetsZero];
Meilbn
  • 582
  • 7
  • 8