我正在使用
CGRectApplyAffineTransform
,我需要有效地判断变换后的矩形是否仍然是矩形,这意味着我不想计算旋转/倾斜的实际值,而只需要检查它是否包含旋转/倾斜。
来自此 API 文档:
https://developer.apple.com/documentation/coregraphics/1455875-cgrectapplyaffinetransform
如果仿射变换 t 仅包含缩放和平移操作,则返回的矩形与由四个变换角构造的矩形重合。
那么我如何有效地判断仿射变换 t 是否仅由缩放和平移操作组成?
文档是正确的:如果仅应用缩放和平移操作,则生成的
CGRect
相当于变换角点。这相当于说如果 b 和 c 为零,则得到的 CGRect
相当于变换角点。
考虑
CGAffineTransform
文档中的公式:
x′ = ax + cy + tx
y′ = bx + dy + ty
如果 b 和 c 为零,则简化为:
x′ = ax + tx
y′ = dy + ty
因此,x′ 是 x 按 a 缩放并由 tx 平移。 同样,y′ 是 y 按 d 缩放并由 ty 平移。
因此,不会发生倾斜,因为 x 和 y 均按各自的标量常数缩放(并由另一个类似的各自标量常数转换)。仅当 x′ 和 y′ 的计算中有多个变量时,才会出现偏差。
有些吹毛求疵:
不过,我可能会犹豫地说,b和c为零意味着没有发生旋转。例如,
CGAffineTransform(a: -1, b: 0, c: 0, d: -1, tx: 0, ty: 0)
表示 180° 旋转。 (它也表示 x 和 y 都变换为 -1;这是同一件事。)这有点学术性,因为我之前对 b 和 c 的观察仍然成立。尽管如此,我还是犹豫是否要说 b 和 c 值为零意味着“不旋转”。
文档只说,如果只进行缩放和平移,那么“返回的矩形与由四个变换角构造的矩形重合”。但这不是唯一的情况,
考虑
CGAffineTransform(a: 0, b: 1, c: -1, d: 0, tx: 0, ty: 0)
。即旋转 90°。这也将产生一个仅由四个变换角构成的矩形。
这是因为如果 a 和 d 为零,则公式再次被简化,但在这种情况下:
x′ = cy + tx
y′ = bx + ty
同样,x′和y′都依赖于单个变量。
因此,总而言之,如果满足以下任一条件,
CGRect
的结果 applying(_:)
将相当于四个角变换的边界框:
例如,
extension CGAffineTransform {
var isEquivalentToTransformedCorners: Bool {
(b == 0 && c == 0) || (a == 0 && d == 0)
}
}
作为最后的替代方案,您可以变换
CGRect
并将其与界定四个变换角的 CGRect
进行比较。上面的比较简单,但这是验证计算的简单方法:
extension CGRect {
func isTransformEquivalentToTransformedCorners(after t: CGAffineTransform) -> Bool {
// transform the rect
let transformed = applying(t)
let boundedCorners = [
CGPoint(x: minX, y: minY).applying(t),
CGPoint(x: maxX, y: minY).applying(t),
CGPoint(x: maxX, y: maxY).applying(t),
CGPoint(x: minX, y: maxY).applying(t)
].boundingRect()
// see if they are the same
return transformed == boundedCorners
}
}
extension Sequence where Element == CGPoint {
func boundingRect() -> CGRect? {
var iterator = makeIterator()
guard let point = iterator.next() else { return nil }
var minX = point.x
var minY = point.y
var maxX = point.x
var maxY = point.y
while let point = iterator.next() {
if point.x < minX {
minX = point.x
} else if point.x > maxX {
maxX = point.x
}
if point.y < minY {
minY = point.y
} else if point.y > maxY {
maxY = point.y
}
}
return CGRect(x: minX, y: minY, width: maxX - minX, height: maxY - minY)
}
}