Rotate image using CGContextDrawImage

Morrowless picture Morrowless · May 27, 2013 · Viewed 10k times · Source

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.

Answer

Peter Hosey picture Peter Hosey · May 28, 2013

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.