31
#define radians(degrees) (degrees * M_PI/180)

UIImage *rotate(UIImage *image) {
  CGSize size = image.size;;

  UIGraphicsBeginImageContext(size);
  CGContextRef context = UIGraphicsGetCurrentContext();

  // If this is commented out, image is returned as it is.
  CGContextRotateCTM (context, radians(90));

  [image drawInRect:CGRectMake(0, 0, size.width, size.height)];
  UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
  UIGraphicsEndImageContext();

  return newImage;
}

Could something else be wrong? Out of ideas.

Gurpartap Singh
  • 2,744
  • 1
  • 26
  • 30
  • Check out the simple and awesome code of Hardy Macia at: http://www.catamount.com/blog/1015/uiimage-extensions-for-cutting-scaling-and-rotating-uiimages/ Thanks Hardy Macia! – Ben Groot May 30 '11 at 13:01

5 Answers5

53

The problem is your context is being rotated around (0,0) which is the top left corner. If you rotate 90 degrees around the top left corner, all your drawing will occur out of the bounds of the context. You need a 2-step transform to move the origin of the context to it's middle, and THEN rotate. Also you need to draw your image centered around the moved/rotated origin, like this:

CGContextTranslateCTM( context, 0.5f * size.width, 0.5f * size.height ) ;
CGContextRotateCTM( context, radians( 90 ) ) ;

[ image drawInRect:(CGRect){ { -size.width * 0.5f, -size.height * 0.5f }, size } ] ;

HTH

nielsbot
  • 15,922
  • 4
  • 48
  • 73
  • Right, it's the axis around which the image rotates. But that code doesn't help. Anyways, figured out the correct. Will post soon as an answer. +1 for the answer. thanks a lot man! – Gurpartap Singh May 12 '11 at 20:17
  • hm.. was it rotate then translate? I confuse the order at times. – nielsbot May 12 '11 at 21:35
  • Thank you! This did the trick for me. I expanded on this by restoring the context afterwards, in the only way I could find. Let me post it in a new answer. – Timo Mar 12 '13 at 16:29
  • Timo--you do that by calling `CGContextSaveGState()` to save the transform matrix, then call `CGContextRestoreGState()` when you are done. – nielsbot Mar 18 '13 at 18:07
11

I try followed code, it works, get from http://www.catamount.com/blog/1015/uiimage-extensions-for-cutting-scaling-and-rotating-uiimages/

+ (UIImage *)rotateImage:(UIImage*)src byRadian:(CGFloat)radian
{
    // calculate the size of the rotated view's containing box for our drawing space
    //UIView *rotatedViewBox = [[UIView alloc] initWithFrame:CGRectMake(0,0, src.size.width, src.size.height)];
    //CGAffineTransform t = CGAffineTransformMakeRotation(radian);
    //rotatedViewBox.transform = t;
    //CGSize rotatedSize = rotatedViewBox.frame.size;
    CGRect rect = CGRectApplyAffineTransform(CGRectMake(0,0, src.size.width, src.size.height), CGAffineTransformMakeRotation(radian));
    CGSize rotatedSize = rect.size;

    // Create the bitmap context
    UIGraphicsBeginImageContext(rotatedSize);
    CGContextRef bitmap = UIGraphicsGetCurrentContext();

    // Move the origin to the middle of the image so we will rotate and scale around the center.
    CGContextTranslateCTM(bitmap, rotatedSize.width/2, rotatedSize.height/2);

    //   // Rotate the image context
    CGContextRotateCTM(bitmap, radian);

    // Now, draw the rotated/scaled image into the context
    CGContextScaleCTM(bitmap, 1.0, -1.0);
    CGContextDrawImage(bitmap, CGRectMake(-src.size.width / 2, -src.size.height / 2, src.size.width, src.size.height), [src CGImage]);

    UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return newImage;
}
andrewchan2022
  • 4,953
  • 45
  • 48
  • It's a bit mad to create a new view just for getting the bounds of a rotated rectangle. You can replace the first 4 lines of code here with simply this: `CGSize rotatedSize = CGSizeMake(src.size.height, src.size.width);`. – Craig McMahon Nov 22 '16 at 11:33
  • @CraigMcMahon you are right. Because my point is to show the CTM feature, so make use the easiest but efficient way to get the bound, it should be perfect when use your code. – andrewchan2022 Nov 22 '16 at 12:13
  • @CraigMcMahon the right way should use like this: `CGSize rotatedSize = CGRectApplyAffineTransform(CGRectMake(0,0, src.size.width, src.size.height), CGAffineTransformMakeRotation(radian));` – andrewchan2022 Nov 22 '16 at 12:18
10

If you don't want to change all the origins and sizes, move the center, rotate and change the center to corner again like this:

CGContextTranslateCTM( context, 0.5f * size.width, 0.5f * size.height ) ; CGContextRotateCTM( context, radians( 90 ) ) ; CGContextTranslateCTM( context, -0.5f * size.width, -0.5f * size.height ) ;

Then draw normally like this:

[image drawInRect:CGRectMake(0, 0, size.width, size.height)];

Farhad Malekpour
  • 1,314
  • 13
  • 16
  • I would really appreciate if you could how this achieves desired result with some illustrations. In particular, is this needed because the rotation happens along the center of the context? – Smart Home Nov 23 '16 at 02:57
1

Expanding on Nielsbot's answer, I created the following snippet. Note that this should preferably be made a function rather than a code snippet, to prevent 'copy/paste programming'. I didn't get to that yet, so feel free to adapt it.

Code snippet:

// <Draw in frame with orientation>
UIImage* imageToDrawRotated = <#image#>;
float rotationAngle = <#angle#>;
CGRect drawFrame = CGRectMake(<#CGFloat x#>, <#CGFloat y#>, <#CGFloat width#>, <#CGFloat height#>);
CGContextTranslateCTM(UIGraphicsGetCurrentContext(), floor(drawFrame.origin.x + drawFrame.size.width/2), floor(drawFrame.origin.y + drawFrame.size.height/2));
CGContextRotateCTM(UIGraphicsGetCurrentContext(), rotationAngle);
[imageToDrawRotated drawInRect:CGRectMake(-floor(drawFrame.size.width/2), -floor(drawFrame.size.height/2), drawFrame.size.width, drawFrame.size.height)];
CGContextRotateCTM(UIGraphicsGetCurrentContext(), -rotationAngle);
CGContextTranslateCTM(UIGraphicsGetCurrentContext(), -floor(drawFrame.origin.x + drawFrame.size.width/2), -floor(drawFrame.origin.y + drawFrame.size.height/2));
// </Draw in frame with orientation>

Also, I welcome a better way to reset the CGContext to its previous state.

Timo
  • 7,992
  • 4
  • 49
  • 67
1

I liked the solution by @lbsweek but I was bit worried about alloc ing UIView just for transform, instead I tried the below

- (UIImage *)rotateImage:(UIImage *)sourceImage byDegrees:(float)degrees
{
    CGFloat radian = degrees * (M_PI/ 180);
    CGRect contextRect = CGRectMake(0, 0, sourceImage.size.width, sourceImage.size.height);
    float newSide = MAX([sourceImage size].width, [sourceImage size].height);
    CGSize newSize =  CGSizeMake(newSide, newSide);
    UIGraphicsBeginImageContext(newSize);
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    CGPoint contextCenter = CGPointMake(CGRectGetMidX(contextRect), CGRectGetMidY(contextRect));
    CGContextTranslateCTM(ctx, contextCenter.x, contextCenter.y);
    CGContextRotateCTM(ctx, radian);
    CGContextTranslateCTM(ctx, -contextCenter.x, -contextCenter.y);
    [sourceImage drawInRect:contextRect];
    UIImage* rotatedImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return rotatedImage;
}
anoop4real
  • 7,598
  • 4
  • 53
  • 56