CGAffineTransform二維檢視旋轉、縮放、平移變換詳解

樊小志發表於2019-03-01

CoreGraphics框架下的CGAffineTransform結構體,可以實現對檢視或圖層的旋轉、縮放、平移及組合變換。

變換demo

實現原理

public struct CGAffineTransform {

    public var a: CGFloat

    public var b: CGFloat

    public var c: CGFloat

    public var d: CGFloat

    public var tx: CGFloat

    public var ty: CGFloat

    public init()

    public init(a: CGFloat, b: CGFloat, c: CGFloat, d: CGFloat, tx: CGFloat, ty: CGFloat)
}
複製程式碼

可以看到CGAffineTransform是一個結構體,它會被組裝成如下矩陣

第三列為(0,0,1)保持不變
檢視原座標(x,y)經過如下變換,轉換為(x',y')
座標變換規則
將該矩陣展開後即得到如下等式,仿射變換完全按照如下關係進行

x' = ax+cy+tx

y' = bx+dy+ty

1. 平移

平移初始化方法如下

/* Return a transform which translates by `(tx, ty)':
         t' = [ 1 0 0 1 tx ty ] */
public init(translationX tx: CGFloat, y ty: CGFloat)
複製程式碼

經過座標變換,可以得到 x' = x+tx y' = y+ty

平移看起來非常簡單,tx為正值則向x軸正向平移,反之向負向平移,ty值同理(x軸向右為正向,y軸向下為正向)

2. 縮放

縮放初始化方法如下

/* Return a transform which scales by `(sx, sy)':
         t' = [ sx 0 0 sy 0 0 ] */
public init(scaleX sx: CGFloat, y sy: CGFloat)
複製程式碼

經過座標變換,可以得到 x' = x*sx y' = y*sy

根據傳入sx, sy進行縮放,傳入負值可進行水平或垂直映象

3. 旋轉

旋轉初始化方法如下

/* Return a transform which rotates by `angle' radians:
         t' = [ cos(angle) sin(angle) -sin(angle) cos(angle) 0 0 ] */
public init(rotationAngle angle: CGFloat)
複製程式碼

這裡入參是一個弧度值,角度x轉換為弧度angle = x*Double.pi/180

旋轉是繞著檢視中心點進行,angle為正值則檢視繞著中心點順時針旋轉

下面推導一下這個變換矩陣

v旋轉θ到達v'
假設v點的座標是(x,y),那麼可以推導得到 v' 點的座標(x’,y’)(設原點到v的距離是r,原點到v點的向量與x軸的夾角是ϕ )

x=rcosϕ, y=rsinϕ

x′=rcos(θ+ϕ), y′=rsin(θ+ϕ)

通過三角函式展開得到

x′=rcosθcosϕ−rsinθsinϕ

y′=rsinθcosϕ+rcosθsinϕ

帶入x和y表示式得到

x′=xcosθ−ysinθ

y′=xsinθ+ycosθ

寫成矩陣的形式即:

v旋轉到v'的變換矩陣
該變換矩陣即初始化中的t'

Demo

我們用一個ImageView來演示一下

1. 平移

jfImageView.transform = CGAffineTransform(translationX: 50, y: -50)
複製程式碼

2. 縮放

jfImageView.transform = CGAffineTransform(scaleX: 2, y: 2)
複製程式碼

3. 旋轉

jfImageView.transform = CGAffineTransform(rotationAngle: -.pi/4)
複製程式碼

4. 組合變換(重點展開說明)

  • 先逆時針旋轉45度,再放大兩倍。
jfImageView.transform = CGAffineTransform(rotationAngle: -.pi/4).scaledBy(x: 2, y: 2)
複製程式碼
  • 先逆時針旋轉45度,再放大兩倍,再沿x軸正向移動50,y軸負向移動50。
jfImageView.transform = CGAffineTransform(rotationAngle: -.pi/4).scaledBy(x: 2, y: 2).translatedBy(x: 50, y: -50)
複製程式碼
  • 先逆時針旋轉45度,再沿x軸正向移動50,y軸負向移動50,再放大兩倍。
jfImageView.transform = CGAffineTransform(rotationAngle: -.pi/4).translatedBy(x: 50, y: -50).scaledBy(x: 2, y: 2)
複製程式碼

後面兩種組合變換得到的結果是不一樣的 先來看三種變換後的檢視frame(4英寸螢幕大小下)

組合變換1之後的frame
組合變換2之後的frame
組合變換3之後的frame

當對圖層做變換的時候,比如旋轉或者縮放,frame實際上代表了覆蓋在圖層旋轉之後的整個軸對齊的矩形區域,也就是說frame的寬高可能和bounds的寬高不再一致了,就如我們的圖片一樣,frame = {6.56, 130.56, 306.88, 306.88},bounds = {0, 0, 128, 89}

旋轉一個檢視或者圖層之後的frame屬性示意圖
變換2相對1豎直向上移動了141.42,變換3相對1豎直向上移動了70.71,兩個變換的x值均未變化。 原因在於當按順序做了變換,上一個變換的結果將會影響之後的變換。

組合變換2的translatedBy(x: 50, y: -50)被逆時針旋轉了45度,放大了兩倍,因此豎直向上移動了100√2≈141.42
組合變換3的translatedBy(x: 50, y: -50)只是被逆時針旋轉了45度,因此豎直向上移動了50√2≈70.71
複製程式碼

Demo下載

參考資料:

Documentation>Core Graphics>CGAffineTransform

旋轉變換(一)旋轉矩陣

《iOS Core Animation》

相關文章