CGAffineTransform 操作顺序不一致
CGAffineTransform operations inconsistency in order applied
我试图了解在 iOS 中使用 CGAffineTransform 链接两个转换的结果。根据 Apple 的文档,结合平移和缩放按预期工作,但结合平移和旋转则不然。
我认为 this post 中的问题与相同的观察结果相同,但我将平移与缩放结合起来以显示不一致的行为。或者是否有一些一致的方法来理解使用这些方法进行转换的顺序?
Apple 的 CGAffineTransform documentation 展示了通过在右侧乘以矩阵来变换由行向量 [x y 1]
表示的点。要使用 CGAffineTransform header 文件中使用的符号,此矩阵为 [a b c d tx ty]
(因为最后一列始终是 [0 0 1]
的转置)。因为矩阵在行向量的右边,如果我们有两个CGAffineTransform矩阵A
和B
,应用于一个点的乘积AB
将首先应用变换A
然后应用操作 B
(这与典型的线性代数书籍所做的相反)。
使用平移变换 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)
should give the transformation AB
, which as noted above performs transformation A
followed by B
. That indeed happens on s.concatenating(t)
, but not t.concatenating(r)
. Based on the example in 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
的 header 文档信息显示
Rotate t by angle radians and return the result:
t = [ cos(angle) sin(angle) -sin(angle) cos(angle) 0 0 ] * t
乘法应该暗示旋转先发生,但措辞使旋转似乎在其次。根据上面的结果,写法是正确的(旋转次之)。
translatedBy
的 header 文档也有将翻译放在第二位的措辞,显示翻译的矩阵乘法放在第一位。但是根据上面的结果,矩阵乘法是正确的(先翻译)。
我这个分析有误吗?还是基于连接的转换顺序与文档中对这些转换和连接方法的描述不一致。
问题是您误解了第一张图:
你说:
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
没有。你的话与图表实际显示的内容相反。
请记住,变换发生在视图的中心周围。好吧,那为什么绿色视图只在其原始中心右侧一点点?
因为我们首先缩小到中心,然后向右移动了10个点——10个点,因为一个点的意义首先被缩小到正常点的1/10。
但是蓝色视图在其原始中心右侧整整 100 点,因为它在缩小之前平移了这 100 点。
我试图了解在 iOS 中使用 CGAffineTransform 链接两个转换的结果。根据 Apple 的文档,结合平移和缩放按预期工作,但结合平移和旋转则不然。
我认为 this post 中的问题与相同的观察结果相同,但我将平移与缩放结合起来以显示不一致的行为。或者是否有一些一致的方法来理解使用这些方法进行转换的顺序?
Apple 的 CGAffineTransform documentation 展示了通过在右侧乘以矩阵来变换由行向量 [x y 1]
表示的点。要使用 CGAffineTransform header 文件中使用的符号,此矩阵为 [a b c d tx ty]
(因为最后一列始终是 [0 0 1]
的转置)。因为矩阵在行向量的右边,如果我们有两个CGAffineTransform矩阵A
和B
,应用于一个点的乘积AB
将首先应用变换A
然后应用操作 B
(这与典型的线性代数书籍所做的相反)。
使用平移变换 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)
should give the transformation AB
, which as noted above performs transformation A
followed by B
. That indeed happens on s.concatenating(t)
, but not t.concatenating(r)
. Based on the example in 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
的 header 文档信息显示
Rotate t by angle radians and return the result:
t = [ cos(angle) sin(angle) -sin(angle) cos(angle) 0 0 ] * t
乘法应该暗示旋转先发生,但措辞使旋转似乎在其次。根据上面的结果,写法是正确的(旋转次之)。
translatedBy
的 header 文档也有将翻译放在第二位的措辞,显示翻译的矩阵乘法放在第一位。但是根据上面的结果,矩阵乘法是正确的(先翻译)。
我这个分析有误吗?还是基于连接的转换顺序与文档中对这些转换和连接方法的描述不一致。
问题是您误解了第一张图:
你说:
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
没有。你的话与图表实际显示的内容相反。
请记住,变换发生在视图的中心周围。好吧,那为什么绿色视图只在其原始中心右侧一点点?
因为我们首先缩小到中心,然后向右移动了10个点——10个点,因为一个点的意义首先被缩小到正常点的1/10。
但是蓝色视图在其原始中心右侧整整 100 点,因为它在缩小之前平移了这 100 点。