CGAffineTransform 操作顺序不一致

Posted

技术标签:

【中文标题】CGAffineTransform 操作顺序不一致【英文标题】:CGAffineTransform operations inconsistency in order applied 【发布时间】:2019-09-15 19:21:30 【问题描述】:

我试图了解在 ios 中使用 CGAffineTransform 链接两个转换的结果。根据 Apple 的文档,平移和缩放的组合按预期工作,但平移和旋转的组合不是。

我认为this post 中的问题是针对相同的观察结果,但我将平移与缩放相结合以显示不一致的行为。或者是否有一些一致的方法来理解使用这些方法进行转换的顺序?

Apple 的 CGAffineTransform documentation 显示了通过在右侧乘以矩阵来转换由行向量 [x y 1] 表示的点。要使用 CGAffineTransform 头文件中使用的符号,这个矩阵是[a b c d tx ty](因为最后一列总是[0 0 1] 的转置)。因为矩阵在行向量的右边,如果我们有两个 CGAffineTransform 矩阵AB,应用于点的乘积AB 将首先应用变换A,然后应用操作@987654335 @(这与典型的线性代数书籍相反)。

使用平移变换 t、缩放变换 s 和旋转变换 r,我检查了生成的变换及其对以下视图的影响:

 s.translatedBy(x: 100, y: 0) // translates first, then scales
 s.concatenating(t) // scales first, then translates
 t.rotated(by: 45 * .pi/180) // translates first, then rotates
 t.concatenating(r) // rotates first, then translates

我了解concatenating 将按照您在执行translatedBy 等操作时看到的相反顺序执行。但是,根据concatenating: documentation,A.concatenatig(B) 应该给出转换AB,如上所述,它执行转换A,然后是B。这确实发生在s.concatenating(t),但不是t.concatenating(r)。根据Matt's iOS book 中的示例,这里有一些设置代码。

 let v1 = UIView(frame:CGRect(20, 111, 132, 194))
 v1.backgroundColor = .red
 view.addSubview(v1)

 let v2 = UIView(frame:v1.bounds)
 v2.backgroundColor = .green
 v1.addSubview(v2)

 let v3 = UIView(frame: v1.bounds)
 v3.backgroundColor = .blue
 v1.addSubview(v3)

 let t = CGAffineTransform(translationX:100, y:0)
 let r = CGAffineTransform(rotationAngle: 45 * .pi/180)
 let s = CGAffineTransform(scaleX: 0.1, y: 0.1)

然后您可以添加此代码以查看平移和缩放是否按预期工作:

 // translates first, then scales
 v2.transform = s.translatedBy(x: 100, y: 0)
 // scales first, then translates
 v3.transform = s.concatenating(t)

Green v2 translates 100 to the right and then is scaled by .1, where blue v3 is scaled by .1 and then translated 100 to the right

但是,平移和旋转的行为是不同的:

 // translates first, then rotates
 v2.transform = t.rotated(by: 45 * .pi/180)
 // rotates first, then translates
 v3.transform = t.concatenating(r)

Green v2 is translated first and then rotated by 45 degrees, where as blue v3 is rotated first and then translated

此外,rotated 的标头文档信息显示

Rotate t by angle radians and return the result:
         t =  [ cos(angle) sin(angle) -sin(angle) cos(angle) 0 0 ] * t

乘法应该意味着旋转首先发生,但措辞使旋转似乎是第二个。根据以上结果,措辞正确(轮换次之)。

translatedBy 的标题文档也有翻译的措辞第二,显示翻译的矩阵乘法是第一。但是根据上面的结果,矩阵乘法是正确的(先平移)。

我在这个分析中犯了错误吗?或者基于连接的转换顺序与这些转换和连接方法的文档中的描述是否存在一些不一致。

【问题讨论】:

我的书很清楚,翻译然后旋转与旋转然后翻译的结果不同,它说明了两者;操作不可交换(顺序很重要)。我展示了连接与一次应用一个连接的工作方式有何不同。那么还有什么不清楚的呢? 所有现代示例代码都在这里:github.com/mattneub/Programming-iOS-Book-Examples/blob/master/… 感谢您的评论。我知道顺序会在转换(以及一般的矩阵乘法)方面有所不同。通过查看两对示例,似乎转换顺序存在不一致。 s.concatenating(t) 先执行s 然后t,但t.concatenating(r) 先执行r 然后t。根据concatenating 的文档,应该是A.concatenating(B) 产生A * B。所以顺序应该始终是A,后跟Bt.concatenating(r) 违反了这一点。 没有不一致。 【参考方案1】:

问题是你误解了第一张图:

你说:

绿色 v2 向右平移 100,然后缩放 0.1,其中蓝色 v3 缩放 0.1,然后向右平移 100

没有。您的话与图表实际显示的内容相反。

请记住,变换发生在视图的中心周围。好的,那为什么绿色视图只在其原始中心的右侧一点点?

这是因为首先我们缩小到中心,然后我们向右移动了 10 个点——10 个点是因为一个点的含义首先被缩小到正常点的 1/10。

但蓝色视图在其原始中心右侧整整 100 点,因为它在缩小之前平移了这 100 点。

【讨论】:

感谢您的反馈。我意识到自己犯了一个错误,并且开始意识到另一个数学概念。我的主要错误如下。显然有两种查看变换的方式,坐标系也是要注意的。例如,绿色的v2可以看作是向右平移100然后缩放0.1(相对于v2的原始坐标)系统)。另一方面,它也可以被视为按 0.1 缩放,然后在 v2 的新坐标系中平移。至于为什么,我还在努力探索。 至于为什么,我想应该可以这样理解:v2的transform相当于t.concatenating(s)。所以矩阵是ts(不是st)。使用x 作为初始点,我们正在考虑x' = x*t*s = s *((s^(-1)*t*s)。第一种方式正如文档描述的那样,这就是为什么转换可以被视为先缩放和平移的原因。但是第二种方式首先显示s,然后是t,但在s之后的新坐标中。这很有趣。困难的是,齐次坐标意味着我们在 R3 中工作 轮换示例中的 v3,v3.transform = t.concatenating(r)。所以矩阵是tr。一种查看方式是向右平移 100,然后旋转(但要根据原始中心进行)。那是我以前没有抓住的。另一种方法是先将其视为旋转,然后根据其新坐标系进行平移。这也有助于我理解rotated(by:)等方法。 我想我已经解释了为什么,无论您选择如何表达,这两个序列的行为方式完全平行。感谢您接受我的回答;我为此遇到了很多麻烦。 您的回答将我推向了正确的方向。我认为我仍然需要做一些工作来了解如何。我这么说是因为根据CGAffineTransform 和concatenating,T1.concatenate(T2) 将应用于点[x y 1] 作为[x' y' 1] = [x y 1] * T1 * T2,这意味着从代数的角度来看,首先应用T1,然后应用T2。您的洞察力是更直观的几何视图,看起来是正确的

以上是关于CGAffineTransform 操作顺序不一致的主要内容,如果未能解决你的问题,请参考以下文章

CGAffineTransform属性

CGAffineTransform

单元格中的 CGAffineTransform 动画不起作用

iphone sdk CGAffineTransform 获取对象的旋转角度

分布式系统中的一致性,与数据库的隔离级别

我如何使用 CGAffineTransform 进行正确的拖放