46

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.

hotpaw2
  • 70,107
  • 14
  • 90
  • 153
  • 2
    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 Nov 26 '11 at 02:49
  • 3
    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 Nov 26 '11 at 02:50
  • 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 Nov 26 '11 at 06:06
  • Perhaps adding detail about your 'requirements' would encourage more help from the community. "Stacked" transforms are more readable, which is generally preferred. – Hyperbole Dec 16 '11 at 16:43
  • It's kinda unhelpful to say that the answers do not meet your specific requirements without mentioning your requirements. – Pascal Dec 17 '11 at 16:22
  • 1
    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 Dec 17 '11 at 21:46
  • the very old comments here are somewhat confusing. you just **set the anchor point**. that's all there is to it – Fattie May 02 '23 at 15:09
  • Setting or moving the anchor point, requires a second step (more than one step), and then the rotational transform used will no longer apply undistorted to the original origin when used for other things. – hotpaw2 May 05 '23 at 17:36

5 Answers5

130

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.

landweber
  • 2,313
  • 1
  • 19
  • 12
  • 1
    'a' is angle in radians it seems – Curtis Dec 10 '12 at 20:18
  • 2
    If you're going to give tips on pronunciation, then you should also specify whether it's -fine as in "fine" or -fine as in "feen". But maybe I'm the only one to get that part wrong. :-/ – Danny Sung Sep 17 '13 at 00:55
  • 2
    This function does not work when rotating more than 180 degrees, the function translates to a net result, always rotating the shortest possible way – Zeezer Nov 22 '13 at 09:02
  • @Zeezer that's a limitation of affine transforms, not this method. – Justin Meiners Apr 26 '21 at 21:23
7

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)
Ming Chu
  • 1,315
  • 15
  • 13
  • Possibly useful if the transform matrix can be extracted and used elsewhere (in C linpack and Metal, etc.) – hotpaw2 Dec 23 '21 at 17:27
4

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);
valvoline
  • 7,737
  • 3
  • 47
  • 52
2

Here's some convenience methods for rotating about an anchor point:

extension CGAffineTransform {
    
    init(rotationAngle: CGFloat, anchor: CGPoint) {
        self.init(
            a: cos(rotationAngle),
            b: sin(rotationAngle),
            c: -sin(rotationAngle),
            d: cos(rotationAngle),
            tx: anchor.x - anchor.x * cos(rotationAngle) + anchor.y * sin(rotationAngle),
            ty: anchor.y - anchor.x * sin(rotationAngle) - anchor.y * cos(rotationAngle)
        )
    }

    func rotated(by angle: CGFloat, anchor: CGPoint) -> Self {
        let transform = Self(rotationAngle: angle, anchor: anchor)
        return self.concatenating(transform)
    }

}
Peter Schorn
  • 916
  • 3
  • 10
  • 20
0

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

view.layer.anchorPoint = CGPoint(x:0,y:1.0)
dijipiji
  • 3,063
  • 1
  • 26
  • 21
  • While this method is intuitive, it is not practical because changing the `anchorPoint` will affect the whole layer/view's transform coordinate space, which will affect unrelated layouts, animations, etc. – John Estropia Jul 27 '23 at 02:58