15

How can I rotate an image drawn by CGContextDrawImage() at its center?

In drawRect:

CGContextSaveGState(c);

rect = CGRectOffset(rect, -rect.size.width / 2, -rect.size.height / 2);
CGContextRotateCTM(c, angle);
CGContextDrawImage(c, rect, doodad.CGImage);
CGContextRotateCTM(c, -angle);
CGContextTranslateCTM(c, pt.x, pt.y);

prevRect = rect;

CGContextRestoreGState(c);

I draw the image at the origin, and rotate it at its center, then translate to where it should be drawn. Not working.

Kjuly
  • 34,476
  • 22
  • 104
  • 118
Morrowless
  • 6,856
  • 11
  • 51
  • 81
  • you can rotate centrally the image drawn view by setting the anchor point – Venk May 27 '13 at 04:16
  • @Tony: This assumes that the image is in a view all by itself (such as an image view), and that the questioner does not intend to save the rotated image anywhere. (Putting it in an image view and rotating that is probably the right solution if the latter assumption is true.) – Peter Hosey May 28 '13 at 01:42

4 Answers4

46

The thing to remember about coordinate transformations—well, one of the things to remember—is that you are not manipulating objects, like the Transform command in a graphics editor; you are manipulating space itself.

Imagine that the device is suspended over a desk by a gooseneck arm, and is fixed in position. Coordinate transformations move the desk around (translation), and turn it one way or the other (rotation), and even squash and stretch it (scaling).

Another of the things to remember about coordinate transformations is that transformation is always relative to the origin. Imagine that your desk starts out with one of its corners—corresponding to a corner of your view—directly underneath the corner of where your view ends up on the screen. Translating moves the corner of the desk, and the rest of the desk with it, sliding it one way or another underneath the screen. Rotating turns the desk around that corner.

One more thing: Transformations affect the future, not the past. Drawing something and then trying to transform it won't work, because you don't transform what you've drawn, you transform the space you're going to draw into. So, we need to move the desk before we can place the image in the right spot.

(I mention that last part because you have a couple of transformation commands that are immediately reverted by your CGContextRestoreGState call. There is no drawing in between for them to affect.)

So, let's start with the unrotated rectangle of the image.

CGRect imageRect = { pointWhereYouWantTheImageCentered, doodad.size };

Now, CGContextDrawImage takes a rectangle, but, as you know, it's going to draw the image from the lower-left corner of that rectangle, not the center. (Hence this whole exercise.)

So, here's what you're going to do. At the end of this, you're going to draw the image not at that point shown above, but at the zero point—that is, on the very corner of the desk. (Not centered on the corner, but with the image's corner on the desk's corner.)

How will that work? It takes some set-up. You're going to have to move your desk.

First, you need to move your desk up and right until the origin corner of your desk is at the desired center point:

CGContextTranslateCTM(context, imageRect.origin.x, imageRect.origin.y);

Then you do the rotation (turning the desk around its origin corner):

CGContextRotateCTM(context, angleInRadians);

Then you move the desk back down and left by half of the image's width and height:

CGContextTranslateCTM(context, imageRect.size.width * -0.5, imageRect.size.height * -0.5);

And then, finally, place the image on the corner of the desk.

CGContextDrawImage(context, (CGRect){ CGPointZero, imageRect.size }, [doodad CGImage]);

With your desk so moved, the center of that rectangle lies directly under the point on the screen where you want the image centered, so the image is so centered.

Peter Hosey
  • 95,783
  • 15
  • 211
  • 370
  • Apologies for my late response and thank you for this comprehensive answer. Your code yields the desired results, but one thing I don't understand: why is the desk origin not at the image center, but at its origin? This code (which I thought would work) seems slightly off: `CGContextTranslateCTM(c, rect.origin.x + rect.size.width * 0.5, rect.origin.y + rect.size.height * 0.5); CGContextRotateCTM(c, angle); CGContextDrawImage(c, (CGRect){CGPointZero, rect.size}, doodad.CGImage);` – Morrowless Jun 10 '13 at 06:34
  • *continued* That translate brings the desk origin to the image center, so rotation is performed about the image center (or so my thinking goes). But apparently it is wrong. – Morrowless Jun 10 '13 at 06:38
  • @Plenilune: `CGContextDrawImage` fills the rectangle you give it with the image (stretching it as needed). The origin of that rectangle—in my version, the zero point—is where one corner of the image will be. I passed the zero point *because* I translated to that corner; the alternative would be to pass the same numbers I passed to `translate` as the origin of the image rectangle instead, in which case the center of the image (whose origin hangs off of the desk) is on the corner (origin) of the desk. – Peter Hosey Jun 10 '13 at 07:12
  • Fab answer. You might need to wrap this in `CGContextSaveGState`/`CGContextRestoreGState`. – Nestor Jun 10 '14 at 10:09
  • @Nestor: True, but only if you want to draw other stuff afterward (in the same drawing pass) without the rotation applied to it. – Peter Hosey Jun 12 '14 at 23:13
  • Question about this: For us it poses a problem (due to other dependencies) to keep the corner of the desk moved. Is there a way to move the desk, perform the rotation, then move the desk back (translating back) prior to drawing? I'm playing around with it now but am curious if you have feedback on this. – mayabelle Aug 24 '16 at 16:23
  • 1
    thats a nice solution. But did anybody notice that the rotated stuff appears bigger on the generated image than it was originally? I have multiple layers (imageview) on top of each other, and when I generate an Image from all of them, all the rotated parts get slightly bigger (I guess thats bcz the frame changes when I rotate the canvas do draw). Is there a way to get around that, and keep the stuff in its original size? – rimes Jun 27 '17 at 23:31
  • It is important to realize that `pointWhereYouWantTheImageCentered` in the example above needs to use the size after the transform is applied. You can find this with `CGRect transformedRect = CGRectApplyAffineTransform(imageBounds, transform)` and `CGPoint pointWhereYouWantTheImageCentered = CGPointMake(transformedRect.size.width / 2.0f, transformedRect.size.height / 2.0f)` – Andrew Jul 08 '18 at 02:22
7

This function can draw image on an existing image with rotation angle in radians.

Swift 3:

extension UIImage {
    func putImage(image: UIImage, on rect: CGRect, angle: CGFloat = 0.0) -> UIImage{

        let drawRect = CGRect(x: 0, y: 0, width: size.width, height: size.height)
        UIGraphicsBeginImageContextWithOptions(drawRect.size, false, 1.0)

        // Start drawing self
        self.draw(in: drawRect)

        // Drawing new image on top
        let context = UIGraphicsGetCurrentContext()!

        // Get the center of new image
        let center = CGPoint(x: rect.midX, y: rect.midY)

        // Set center of image as context action point, so rotation works right
        context.translateBy(x: center.x, y: center.y)
        context.saveGState()

        // Rotate the context
        context.rotate(by: angle)

        // Context origin is image's center. So should draw image on point on origin
        image.draw(in: CGRect(origin: CGPoint(x: -rect.size.width/2, y: -rect.size.height/2), size: rect.size), blendMode: .normal, alpha:
1.0)

        // Go back to context original state.
        context.restoreGState()

        // Get new image
        let newImage = UIGraphicsGetImageFromCurrentImageContext()!
        UIGraphicsEndImageContext()

        return newImage
    }

}

If you need to create and empty image with size and color, use:

extension UIImage {
    convenience init?(size: CGSize, color: UIColor) {
        let rect = CGRect(origin: .zero, size: size)
        UIGraphicsBeginImageContextWithOptions(rect.size, true, 1.0)
        color.setFill()
        UIRectFill(rect)
        let image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()

        guard let cgImage = image?.cgImage else { return nil }
        self.init(cgImage: cgImage)
    }
}
akoruk
  • 195
  • 2
  • 8
0

You can use the following method for rotating image.

-(UIImage*)rotateImage:(UIImage*)img forAngle:(CGFloat)radian   {

    UIView *rotatedViewBox = [[UIView alloc]initWithFrame:CGRectMake(0, 0, img.size.width, img.size.height)];
    CGAffineTransform t = CGAffineTransformMakeRotation(radian);
    rotatedViewBox.transform = t;
    CGSize rotatedSize = rotatedViewBox.frame.size;

    UIGraphicsBeginImageContext(rotatedSize);

    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextTranslateCTM(context, rotatedSize.width, rotatedSize.height);
    CGContextRotateCTM(context, radian);
    CGContextScaleCTM(context, 1.0, -1.0);

    CGContextDrawImage(context, CGRectMake(-img.size.width, -img.size.height, img.size.width, img.size.height), img.CGImage);
    UIImage *returnImg = UIGraphicsGetImageFromCurrentImageContext();

    UIGraphicsEndImageContext();
    return returnImg;
}

If you use angle, then here is the macro for converting degree to radian

#define DEGREES_TO_RADIANS(angle) ((angle) / 180.0 * M_PI)
manujmv
  • 6,450
  • 1
  • 22
  • 35
0

If you're ultimately drawing to the screen (as opposed to generating an image to save to a file), you might consider not using a CGContext at all, but rather putting the image into a CALayer and rotating the layer about its anchor point:

[layer setValue:@(rotationInRadians) forKeyPath:@"transform.rotation.z"];

If the image is itself generated (as opposed to a static resource from your bundle or something user-supplied), you might consider making it out of layers and setting the sublayerTransform of their parent/common-ancestor layer:

[layerContainingTheConstituentLayers setValue:@(rotationInRadians) forKeyPath:@"sublayerTransform.rotation.z"];

(There may be ways to do this with views instead of layers; I'm a Mac guy, so I don't know UIView very deeply.)

Peter Hosey
  • 95,783
  • 15
  • 211
  • 370