Skip to content

Commit cfed8d0

Browse files
committed
[AffineTransform] Explain the operation calculations.
1 parent 3a31d34 commit cfed8d0

File tree

1 file changed

+80
-13
lines changed

1 file changed

+80
-13
lines changed

Sources/Foundation/AffineTransform.swift

Lines changed: 80 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -132,27 +132,28 @@ extension AffineTransform {
132132
}
133133

134134
extension AffineTransform {
135-
/// Creates an affine transformation matrix by combining the receiver with `transformStruct`.
136-
/// That is, it computes `T * M` and returns the result, where `T` is the receiver's and `M` is
137-
/// the `transformStruct`'s affine transformation matrix.
138-
/// The resulting matrix takes the following form:
135+
/// Creates an affine transformation matrix by combining the two matrices `A×B` and returns the result.
139136
///
140-
/// ```swift
141-
/// [ m11_T m12_T 0 ] [ m11_M m12_M 0 ]
142-
/// T * M = [ m21_T m22_T 0 ] [ m21_M m22_M 0 ]
143-
/// [ tX_T tY_T 1 ] [ tX_M tY_M 1 ]
144-
/// ```
137+
/// The resulting matrix takes the following form
145138
///
146139
/// ```swift
147-
/// [ (m11_T*m11_M + m12_T*m21_M) (m11_T*m12_M + m12_T*m22_M) 0 ]
148-
/// = [ (m21_T*m11_M + m22_T*m21_M) (m21_T*m12_M + m22_T*m22_M) 0 ]
149-
/// [ (tX_T*m11_M + tY_T*m21_M + tX_M) (tX_T*m12_M + tY_T*m22_M + tY_M) 1 ]
140+
///
141+
/// [ a1, b1, 0 ] [ a2, b2, 0 ]
142+
/// A×B = [ c1, d1, 0 ] × [ c2, d2, 0 ]
143+
/// [ x1, y1, 1 ] [ x2, y2, 1 ]
144+
///
145+
/// [ a1*a2+b1*c2+0*x2 a1*b2+b1*d2+0*y2 a1*0+b1*0+0*1 ]
146+
/// A×B = [ c1*a2+d1*c2+0*x2 c1*b2+d1*d2+0*y2 c1*0+d1*0+0*1 ]
147+
/// [ x1*a2+y1*c2+1*x2 x1*b2+y1*d2+1*y2 x1*0+y1*0+1*1 ]
148+
///
149+
/// [ a1*a2+b1*c2 a1*b2+b1*d2 0 ]
150+
/// A×B = [ c1*a2+d1*c2 c1*b2+d1*d2 0 ]
151+
/// [ x1*a2+y1*c2+x2 x1*b2+y1*d2+y2 1 ]
150152
/// ```
151153
@inline(__always)
152154
internal func concatenated(_ other: AffineTransform) -> AffineTransform {
153155
let (t, m) = (self, other)
154156

155-
// this could be optimized with a vector version
156157
return AffineTransform(
157158
m11: (t.m11 * m.m11) + (t.m12 * m.m21), m12: (t.m11 * m.m12) + (t.m12 * m.m22),
158159
m21: (t.m21 * m.m11) + (t.m22 * m.m21), m22: (t.m21 * m.m12) + (t.m22 * m.m22),
@@ -224,8 +225,59 @@ extension AffineTransform {
224225
extension AffineTransform {
225226
/// Returns an inverted version of the matrix if possible, or nil if not.
226227
public func inverted() -> AffineTransform? {
228+
// We need the matrix of cofactors to calculate the inverse, but first we
229+
// need to calculate the minors of each element — where the minor of an
230+
// element Ai,j is the determinant of the matrix derived from deleting
231+
// the ith row and jth column:
232+
//
233+
// [ |d y| |c x| |c x| ]
234+
// [ |0 1| |0 1| |d y| ]
235+
// [ ]
236+
// [ |b y| |a x| |a x| ]
237+
// M = [ |0 1| |0 1| |b y| ]
238+
// [ ]
239+
// [ |b d| |a c| |a c| ]
240+
// [ |0 0| |0 0| |b d| ]
241+
//
242+
// [ d*1-y*0 c*1-x*0 c*y-x*d ]
243+
// M = [ b*1-y*0 a*1-x*0 a*y-x*b ]
244+
// [ b*0-d*0 a*0-c*0 a*d-c*b ]
245+
//
246+
// [ d c c*y-x*d ]
247+
// M = [ b a a*y-x*b ]
248+
// [ 0 0 |A| ]
249+
//
250+
// Now we can calculate the matrix of cofactors by negating each element Ai,j
251+
// where i+j is odd:
252+
//
253+
// [ d -c c*y-x*d ]
254+
// C = [ -b a -(a*y-x*b) ]
255+
// [ 0 -0 |A| ]
256+
//
257+
// Next, we can find the adjugate matrix, which is the transposed matrix of
258+
// cofactors — a matrix whose ith column is the ith row of the matrix of C:
259+
//
260+
// [ d -b 0 ]
261+
// adj(A) = [ -c a -0 ]
262+
// [ c*y-x*d -(a*y-x*b) |A| ]
263+
//
264+
// Finally, the inverse matrix is the product of the reciprocal of the determinant
265+
// of A times adj(A), assuming that |A|≠0:
266+
//
267+
// A^-1 = (1 / |A|) × adj(A)
268+
//
269+
// [ d/|A| -b/|A| 0/|A| ]
270+
// A^-1 = [ -c/|A| a/|A| -0/|A| ]
271+
// [ (c*y-x*d)/|A| -(a*y-x*b)/|A| |A|/|A| ]
272+
//
273+
// [ d/|A| -b/|A| 0 ]
274+
// A^-1 = [ -c/|A| a/|A| 0 ]
275+
// [ (c*y-x*d)/|A| (x*b-a*y)/|A| 1 ]
276+
227277
let determinant = (m11 * m22) - (m12 * m21)
228278

279+
// We compare to ulp of 0 instead of doing determinant != 0,
280+
// to catch floating-point rounding errors.
229281
if abs(determinant) <= CGFloat.zero.ulp {
230282
return nil
231283
}
@@ -260,6 +312,15 @@ extension AffineTransform {
260312
extension AffineTransform {
261313
/// Applies the transform to the specified point and returns the result.
262314
public func transform(_ point: CGPoint) -> CGPoint {
315+
// Multiply the given point matrix with the matrix:
316+
//
317+
// [ m11 m12 0 ]
318+
// [ x' y' 1 ] = [ x y 1 ] × [ m21 m22 0 ]
319+
// [ tX tY 1 ]
320+
//
321+
// [ x' y' 1 ] = [ x*m11+y*m21+1*tX x*m12+y*m22+1*tY x*0+y*0+1*1 ]
322+
//
323+
// [ x' y' 1 ] = [ x*m11+y*m21+tX x*m12+y*m22+tY 1 ]
263324
CGPoint(
264325
x: (m11 * point.x) + (m21 * point.y) + tX,
265326
y: (m12 * point.x) + (m22 * point.y) + tY
@@ -268,6 +329,12 @@ extension AffineTransform {
268329

269330
/// Applies the transform to the specified size and returns the result.
270331
public func transform(_ size: CGSize) -> CGSize {
332+
// Multiply the given size matrix with the scale & rotation matrix:
333+
//
334+
// [ w' h' ] = [ w h ] * [ m11 m12 ]
335+
// [ m21 m22 ]
336+
//
337+
// [ w' h' ] = [ w*m11+h*m21 w*m12+h*m22 ]
271338
CGSize(
272339
width : (m11 * size.width) + (m21 * size.height),
273340
height: (m12 * size.width) + (m22 * size.height)

0 commit comments

Comments
 (0)