43
votes

How can I make a Core Graphics affine transform for rotation around a point x,y of angle a, using only a single call to CGAffineTransformMake() plus math.h trig functions such as sin(), cos(), etc., and no other CG calls.

Other answers here seem to be about using multiple stacked transforms or multi-step transforms to move, rotate and move, using multiple Core Graphics calls. Those answers do not meet my specific requirements.

4
Why do you need a single call to CGAffineTransformMake()? The stacked calls produce the exact same results, except they do so in a way that is readable and makes sense. If you really want to do it in a single call, you're just going to end up replicating the same math used in the stacked calls, for absolutely not benefit.Lily Ballard
What requirements could that be? You can combine those "stacked" transformations into one CGAffineTransform using CGAffineTransformConcat. The result will be the same as formulas for the individual components, and the computations involved will be the same internally, or possibly more optimized in the case of CGAffineTransformConcat.morningstar
I want the matrix equations to use on a matching but non graphics model object in a different (not necessarily Euclidean) geometric 2D space.hotpaw2
Perhaps adding detail about your 'requirements' would encourage more help from the community. "Stacked" transforms are more readable, which is generally preferred.Hyperbole
See the accepted answer for the details required: the sine and cosine equations needed for a one-step transform, which can be efficiently used to directly compute the locations of multiple points in a 2D physical model outside of any graphics context, as well as rotating the image in the graphics context to exactly match. Readability was not among my stated requirements.hotpaw2

4 Answers

123
votes

A rotation of angle a around the point (x,y) corresponds to the affine transformation:

CGAffineTransform transform = CGAffineTransformMake(cos(a),sin(a),-sin(a),cos(a),x-x*cos(a)+y*sin(a),y-x*sin(a)-y*cos(a));

You may need to plug in -a instead of a depending on whether you want the rotation to be clockwise or counterclockwise. Also, you may need to plug in -y instead of y depending on whether or not your coordinate system is upside down.

Also, you can accomplish precisely the same thing in three lines of code using:

CGAffineTransform transform = CGAffineTransformMakeTranslation(x, y);
transform = CGAffineTransformRotate(transform, a);
transform = CGAffineTransformTranslate(transform,-x,-y);

If you were applying this to a view, you could also simply use a rotation transform via CGAffineTransformMakeRotation(a), provided you set the view's layer's anchorPoint property to reflect the point you want to rotate around. However, is sounds like you aren't interested in applying this to a view.

Finally, if you are applying this to a non-Euclidean 2D space, you may not want an affine transformation at all. Affine transformations are isometries of Euclidean space, meaning that they preserve the standard Euclidean distance, as well as angles. If your space is not Euclidean, then the transformation you want may not actually be affine, or if it is affine, the matrix for the rotation might not be as simple as what I wrote above with sin and cos. For instance, if you were in a hyperbolic space, you might need to use the hyperbolic trig functions sinh and cosh, along with different + and - signs in the formula.

P.S. I also wanted to remind anyone reading this far that "affine" is pronounced with a short "a" as in "ask", not a long "a" as in "able". I have even heard Apple employees mispronouncing it in their WWDC talks.

6
votes

for Swift 4

print(x, y) // where x,y is the point to rotate around
let degrees = 45.0
let transform = CGAffineTransform(translationX: x, y: y)
    .rotated(by: degrees * .pi / 180)
    .translatedBy(x: -x, y: -y)
3
votes

For those like me, that are struggling in search of a complete solution to rotate an image and scale it properly, in order to fill the containing frame, after a couple of hours this is the most complete and flawless solution that I have obtained.

The trick here is to translate the reference point, before any trasformation involved (both scale and rotation). After that, you have to concatenate the two transform in order to obtain a complete affine transform.

I have packed the whole solution in a CIFilter subclass that you can gist here.

Following the relevant part of code:

CGFloat a = _inputDegree.floatValue;
CGFloat x = _inputImage.extent.size.width/2.0;
CGFloat y = _inputImage.extent.size.height/2.0;

CGFloat scale = [self calculateScaleForAngle:GLKMathRadiansToDegrees(a)];

CGAffineTransform transform = CGAffineTransformMakeTranslation(x, y);
transform = CGAffineTransformRotate(transform, a);
transform = CGAffineTransformTranslate(transform,-x,-y);


CGAffineTransform transform2 = CGAffineTransformMakeTranslation(x, y);
transform2 = CGAffineTransformScale(transform2, scale, scale);
transform2 = CGAffineTransformTranslate(transform2,-x,-y);

CGAffineTransform concate   = CGAffineTransformConcat(transform2, transform);
-1
votes

Use the view's layer and anchor point. e.g.

view.layer.anchorPoint = CGPoint(x:0,y:1.0)