围绕一个点旋转的一步仿射变换?

46

我如何使用单个CGAffineTransformMake()调用以及数学函数如sin(), cos()等,而不使用其他CG调用,制作一个绕点(x, y)旋转角度a的Core Graphics仿射变换。

其他答案似乎是关于使用多个堆叠的变换或多步变换来移动、旋转和移动,使用多个Core Graphics调用。这些答案不符合我的特定要求。


2
为什么你需要使用CGAffineTransformMake()这个函数进行单独调用呢?堆叠的调用会产生完全相同的结果,只不过以一种可读且有意义的方式展示。如果你真的想要在一个调用中完成,那么你最终只是在重复使用堆叠调用中所使用的相同数学运算,而没有任何好处。 - Lily Ballard
3
那需要什么样的要求呢?您可以使用CGAffineTransformConcat将那些“叠加”的变换组合成一个CGAffineTransform。其结果与单个组件公式相同,所涉及的计算在内部也是相同的,或者在使用CGAffineTransformConcat时可能会更优化。 - morningstar
也许添加关于您的“需求”的详细信息会鼓励社区提供更多帮助。 “堆叠”变换更易读,通常更受欢迎。 - Hyperbole
如果不提及您的要求,仅仅说答案不符合您的特定要求是没有帮助的。 - Pascal
1
请查看被接受的答案以获取所需细节:正弦和余弦方程式,用于一步变换,可以高效地直接计算2D物理模型中多个点的位置,而不需要任何图形上下文,同时旋转图像以精确匹配图形上下文。可读性并非我所要求的要素。 - hotpaw2
显示剩余4条评论
5个回答

130

绕点(x,y)旋转角度a对应于仿射变换:

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));

根据你想要的旋转方向,你可能需要使用-a而不是a。此外,根据你的坐标系统是否倒置,你可能需要使用-y而不是y。

另外,你可以使用以下三行代码实现完全相同的效果:

CGAffineTransform transform = CGAffineTransformMakeTranslation(x, y);
transform = CGAffineTransformRotate(transform, a);
transform = CGAffineTransformTranslate(transform,-x,-y);
如果您要将此应用于视图,则还可以通过CGAffineTransformMakeRotation(a)使用旋转变换,只需设置视图的图层锚点属性以反映您要围绕旋转的点即可。但是,听起来您并不想将其应用于视图。
最后,如果您将其应用于非欧几里得二维空间,则可能根本不需要仿射变换。仿射变换是欧几里得空间的等距变换,这意味着它们保留标准欧几里得距离以及角度。如果您的空间不是欧几里得空间,则您想要的变换实际上可能不是仿射变换,或者如果它是仿射变换,则旋转的矩阵可能不像我上面用sin和cos写的那样简单。例如,如果您处于双曲空间中,则可能需要使用双曲三角函数sinh和cosh以及公式中不同的+和-符号。
顺便说一句,我还想提醒任何阅读到这里的人,“仿射”发音为短“a”,就像“ask”一样,而不是长“a”,就像“able”一样。我甚至听到过Apple公司员工在他们的WWDC演讲中发音错误。

1
'a' 看起来是以弧度为单位的角度 - Curtis
2
如果你要给发音技巧的建议,那么你也应该明确是“fine”还是“feen”。但也许只有我会弄错这一部分。 :-/ - Danny Sung
2
当旋转角度超过180度时,此函数无法正常工作。该函数会将其翻译为净结果,始终以最短的可能方式旋转。 - Zeezer
@Zeezer,那是仿射变换的限制,而不是这种方法的限制。 - Justin Meiners

7

针对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)

如果可以提取并在其他地方使用变换矩阵(如C linpack和Metal等),则可能会很有用。 - hotpaw2

4

对于像我这样的人,他们正在苦苦寻找完整的解决方案来旋转并适当缩放图像,以便填充包含框架,在几个小时后,这是我得到的最完整和无瑕疵的解决方案。

这里的技巧是在任何变换(包括比例和旋转)之前翻译参考点。之后,您必须连接两个变换以获得完整的仿射变换。

我已经将整个解决方案打包在一个CIFilter子类中,您可以在gist here

以下是相关代码部分:

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);

2

以下是一些关于围绕锚点旋转的便捷方法:

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)
    }

}

0
使用视图的图层和锚点。例如:
view.layer.anchorPoint = CGPoint(x:0,y:1.0)

这个解决方案非常完美!谢谢! - Nadzeya
绝对正确的答案。 - Fattie
1
但仍然显示-1(叹气……) - dijipiji
我已经做到了。 - 2nebin
虽然这种方法很直观,但并不实用,因为改变anchorPoint会影响整个图层/视图的变换坐标空间,从而影响到与之无关的布局、动画等。 - John Estropia

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接