拆分CGAffineTransform为旋转、缩放和平移组件后重组变换无法匹配原变换的问题排查
问题分析与解决方案
首先,你的问题出在两个核心点上:变换分解的计算逻辑错误,以及忽略了原变换包含剪切(Shear)分量,同时变换拼接的顺序也需要匹配分解逻辑。
1. 为什么还原结果和原变换不一致?
你的测试变换__CGAffineTransformMake(2.078, 3.459, 1.676, 0, 591, 397)并不是单纯的「旋转+缩放+平移」组合——它包含了剪切变换。标准的无剪切仿射变换满足b = -c * (scaleY/scaleX)(正交变换的核心条件),但你的变换里b=3.459、c=1.676,显然不符合这个条件,所以只分解旋转、缩放、平移是无法完整还原原变换的。
另外,你的scaleXY()方法计算逻辑有误:当前的计算把旋转的角度分量也混入了缩放值里,正确的缩放应该是在消除旋转影响后提取的。
2. 正确的仿射变换分解(包含剪切)
如果要完整分解任意CGAffineTransform为「平移+旋转+缩放+剪切」四个分量,可以使用以下扩展:
public extension CGAffineTransform { // 直接提取平移分量 var translation: CGPoint { return CGPoint(x: tx, y: ty) } // 提取旋转弧度(基于矩阵的旋转分量) var rotationRadians: CGFloat { return atan2(b, a) } // 消除旋转影响后提取缩放分量 var scale: CGPoint { let cosθ = cos(rotationRadians) let sinθ = sin(rotationRadians) let scaleX = a * cosθ + b * sinθ let scaleY = d * cosθ - c * sinθ return CGPoint(x: scaleX, y: scaleY) } // 提取剪切分量 var shear: CGPoint { let cosθ = cos(rotationRadians) let sinθ = sin(rotationRadians) let shearX = -a * sinθ + b * cosθ let shearY = d * sinθ + c * cosθ return CGPoint(x: shearX, y: shearY) } // 从分量重建变换(注意拼接顺序的逻辑) static func from(translation: CGPoint, rotation: CGFloat, scale: CGPoint, shear: CGPoint) -> CGAffineTransform { let translationT = CGAffineTransform(translationX: translation.x, y: translation.y) let shearT = CGAffineTransform(a: 1, b: shear.y, c: shear.x, d: 1, tx: 0, ty: 0) let scaleT = CGAffineTransform(scaleX: scale.x, y: scale.y) let rotationT = CGAffineTransform(rotationAngle: rotation) // 变换拼接是右乘逻辑:先应用最右侧的变换,再依次向左 // 这里的顺序对应:旋转 → 缩放 → 剪切 → 平移 return translationT.concatenating(shearT).concatenating(scaleT).concatenating(rotationT) } }
3. 测试修正后的代码
用你的测试变换验证,重建后的变换会和原变换完全一致(浮点精度范围内):
let randomTransform = __CGAffineTransformMake(2.078, 3.459, 1.676, 0, 591, 397) let translation = randomTransform.translation let rotation = randomTransform.rotationRadians let scale = randomTransform.scale let shear = randomTransform.shear let reconstructedTransform = CGAffineTransform.from(translation: translation, rotation: rotation, scale: scale, shear: shear) NSLog("Reconstructed: \(reconstructedTransform), Original: \(randomTransform)")
4. 如果你的场景只需要无剪切变换?
如果你确保自己的变换都是无剪切的(比如仅由旋转、缩放、平移组合而成),可以简化分解,同时注意拼接顺序:
- 无剪切变换的判断条件:
a*d == b*c(行列式等于缩放分量的乘积) - 正确的拼接顺序:由于
concatenating是右乘逻辑,若原变换是「旋转→缩放→平移」的顺序构建,重建时要写成:let newTransform = translationTransform.concatenating(scalingTransform).concatenating(rotationTransform)
内容的提问来源于stack exchange,提问作者Deepak Sharma
相关产品推荐
相关产品推荐

