CGAffineTransform 如何确定它只包含缩放/平移而不包含倾斜/旋转

问题描述 投票:0回答:1

我正在使用

CGRectApplyAffineTransform
,我需要有效地判断变换后的矩形是否仍然是矩形,这意味着我不想计算旋转/倾斜的实际值,而只需要检查它是否包含旋转/倾斜。

来自此 API 文档:

https://developer.apple.com/documentation/coregraphics/1455875-cgrectapplyaffinetransform

如果仿射变换 t 仅包含缩放和平移操作,则返回的矩形与由四个变换角构造的矩形重合。

那么我如何有效地判断仿射变换 t 是否仅由缩放和平移操作组成?

ios core-graphics
1个回答
0
投票

文档是正确的:如果仅应用缩放和平移操作,则生成的

CGRect
相当于变换角点。这相当于说如果 bc 为零,则得到的
CGRect
相当于变换角点。

考虑

CGAffineTransform
文档中的公式:

x′ = ax + cy + tx
y′ = bx + dy + ty

如果 bc 为零,则简化为:

x′ = ax + tx
y′ = dy + ty

因此,x′xa 缩放并由 tx 平移。 同样,y′yd 缩放并由 ty 平移。

因此,不会发生倾斜,因为 xy 均按各自的标量常数缩放(并由另一个类似的各自标量常数转换)。仅当 x′y′ 的计算中有多个变量时,才会出现偏差。


有些吹毛求疵:

  1. 不过,我可能会犹豫地说,bc为零意味着没有发生旋转。例如,

    CGAffineTransform(a: -1, b: 0, c: 0, d: -1, tx: 0, ty: 0)
    表示 180° 旋转。 (它也表示 xy 都变换为 -1;这是同一件事。)这有点学术性,因为我之前对 bc 的观察仍然成立。尽管如此,我还是犹豫是否要说 bc 值为零意味着“不旋转”。

  2. 文档只说,如果只进行缩放和平移,那么“返回的矩形与由四个变换角构造的矩形重合”。但这不是唯一的情况,

    考虑

    CGAffineTransform(a: 0, b: 1, c: -1, d: 0, tx: 0, ty: 0)
    。即旋转 90°。这也将产生一个仅由四个变换角构成的矩形。

    这是因为如果 ad 为零,则公式再次被简化,但在这种情况下:

    x′ = cy + tx
    y′ = bx + ty

    同样,x′y′都依赖于单个变量。


因此,总而言之,如果满足以下任一条件,

CGRect
的结果
applying(_:)
将相当于四个角变换的边界框:

  • bc都为零;和/或
  • ad 均为零。

例如,

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)
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.