详细解析 CGAffineTransform 矩阵

CGAffineTransform 是对于仿射变换矩阵进行了封装,而要理解仿射变换(affine transformation)先要理解线性变换(linear transformation)

线性变换

教材中的定义

设V, U 分别是n维和m维线性空间,T是一个从V到U的映射,如果T满足
1.任意V中向量 α1、α2,有T(α1 + α2) = T(α1)+T(α2)
2.任意V中向量 α,有T(λα) = λT(α)
那么,T就称为从V到U的线性映射,或线性变换

简而言之线性变换就是把一个线性空间的向量映射到另一个线性空间去,而且这个映射具备线性原则,即
1.两个向量之和的映射 = 两个向量映射的和
2.(向量 * 常数) 的映射 = (向量的映射) * 常数

根据这样的定义可知线性变换的特点
1.直线经过变换依然是直线
2.原点不变(T(0) = 0)
3.根据矩阵的性质,任何n维实数空间的线性变换都可以用矩阵来表示即 T(x) = Ax

二维空间内的线性变换

当V = U = 二维线性空间时,就可以理解为二维空间内部的坐标变换

用矩阵表示为(采用与CGAffineTransform相同的矩阵,可能与教材给定的方式有差异,但本质相同):
\begin{bmatrix} x'&y' \end{bmatrix} = \begin{bmatrix} x &y \end{bmatrix} * \begin{bmatrix} a & b\\ c & d \end{bmatrix}
列出对应的方程组:
x' = ax + cy \\ y' = bx + dy

二维空间内的线性变换可以实现很多效果

旋转

\left[ \begin{matrix} x'&y' \end{matrix} \right] = \left[ \begin{matrix} x & y \end{matrix} \right] * \left[ \begin{matrix} cosφ & sinφ \\ -sinφ & cosφ \end{matrix} \right]

缩放

\left[ \begin{matrix} x'&y' \end{matrix} \right] = \left[ \begin{matrix} x & y \end{matrix} \right] * \left[ \begin{matrix} xScale & 0\\ 0 & yScale \end{matrix} \right]

对称

\left[ \begin{matrix} x'&y' \end{matrix} \right] = \left[ \begin{matrix} x & y \end{matrix} \right] * \left[ \begin{matrix} a & b\\ c & d \end{matrix} \right]
1.当 a=1, d=-1, b=c=0时
沿x轴对称

2.当 a=-1, d=1, b=c=0时
沿y轴对称

3.当 a=-1,d=-1, b=c=0时
原点对称

4.当 a=d=0, b=c=1时
沿 y=x 对称

错切(平行四边形化)

\left[ \begin{matrix} x'&y' \end{matrix} \right] = \left[ \begin{matrix} x & y \end{matrix} \right] * \left[ \begin{matrix} a & b\\ c & d \end{matrix} \right]
1.当 a=d=1, b=0, c为任意 时
沿x方向错切

2.当 a=d=1, c=0, b为任意 时
沿y方向错切

注:以上均可通过 let transform = CGAffineTransform(a: a, b: b, c: c, d: d, tx: 0, ty: 0)来实现

仿射变换

CGAffineTransform 除了 a,b,c,d 还包含tx,ty。之所以二维空间的变换要使用三维矩阵,是因为二维空间线性变换没办法实现平移的效果,需要借助三维空间的线性变换,再映射到二维空间,形成二维空间的平移效果。

维基百科的动态图,较好的展示了这一点


rotate.gif

CGAffineTransform

\left[ \begin{matrix} x'&y'& 1 \end{matrix} \right] = \left[ \begin{matrix} x & y & 1 \end{matrix} \right] * \left[ \begin{matrix} a & b & 0\\ c & d & 0\\ tx& ty& 1 \end{matrix} \right]
\begin{aligned} x' = ax + cy + tx \\ y' = bx + dy + ty \end{aligned}

a b c d tx ty 常用含义整理

由上可知 tx,ty只负责平移,而a,b,c,d在不同操作下具有不同含义

只有旋转时

a = d = cosφ
b = sinφ
c = -sinφ

只有缩放时

a: x方向缩放系数
d: y方向缩放系数

旋转与缩放复合时

复合情况下,当前transfrom中 a,b,c,d 是多个矩阵相乘后的结果

缩放系数:行列式值 ---- 科普原因

即:

public func scale() -> CGFloat{
    let scale = sqrt(a*d - b*c)
    return scale
}

旋转弧度:
因为复合情况下a为 scale 与 cosφ 的乘积,所以可得:
\begin{aligned} cosφ = a / scale \\ φ = arccos(a / scale) \end{aligned}

/// -π ~ π
public func rotate() -> CGFloat{
     let scale = sqrt(a*d - b*c)
     if scale == 0 {
         return 0
     }
     var rotate = acos(a/scale)
     //由于rotate >= 0 && <= π, 当旋转超过π或为负时,要加入符号判断
     if (b < 0) {
         rotate = -rotate
     }
     return rotate
 }
    

transform, anchorPoint, frame

对transform进行变换时,frame会发生变化,但这种变化不是直接将frame这个长方形内的坐标同时变换成新的frame,而是先在其 ****自身坐标系**** 内所有的点进行transform变换,然后再更新到其frame的变化

而自身坐标系的原点,就是其anchorPoint,因为anchorPoint默认(0.5,0.5),也就是其中心点。

缩放并改变anchorPoint后frame的变化

scale.gif

在这个transform进行x, y 同时放大2倍的过程中,其frame的变化

缩放前frame(137.5, 283.5, 100.0, 100.0)
缩放后frame(87.5, 233.5, 200.0, 200.0)

还是上面的view,现将其anchorPoint改为(0, 0),在对其进行上面的x, y 放大2倍的过程,推算一下frame最终会变为多少。

frame的计算原理

frame.origin.x = position.x - anchorPoint.x * bounds.size.width

frame.origin.y = position.y - anchorPoint.y * bounds.size.height

1.ahchorPoint变为 (x: 0, y: 0)
2.anchorPoint改变不影响position,position = (x: 187.5, y:333.5)
3.由上述frame计算原理可得 frame = (187.5, 333.5, 100, 100)
4.因为其自身坐标系坐标为(0,0,100,100),缩放后为(0,0,200,200), 转化为frame = (187.5, 333.5, 200, 200)

运行结果一致

缩放前frame(137.5, 283.5, 100.0, 100.0)
修改anchorPoint为0后的frame(187.5, 333.5, 100.0, 100.0)
缩放后frame(187.5, 333.5, 200.0, 200.0)

不改变anchorPoint而修改旋转中心点的方式

在对transform进行旋转或缩放等操作时,修改anchorPoing可以修改旋转或缩放的中心,这是我们常用的操作,但是anchorPoint的修改直接会导致元素位置变化,所以做完transform变化后通常还要恢复anchorPoint。

要想transform中心点变化还不改变anchorPoint,可以通过先平移后旋转从而达到和修改anchorPoint一样的效果

/// 生成绕任意点旋转的transfrom
/// - Parameters:
///   - radian: 旋转弧度
///   - circleCenter: 旋转中心
///   - viewCenter: 被旋转view的中心
private func rotateTransform(radian:CGFloat, circleCenter:CGPoint, viewCenter: CGPoint) -> CGAffineTransform {
        /// 转化为将旋转的view 自身的坐标系
        let x = circleCenter.x - viewCenter.x
        let y = circleCenter.y - viewCenter.y
        
        let transform = CGAffineTransform(a: cos(radian), b: sin(radian), c: -sin(radian), d: cos(radian), tx: x - x*cos(radian)+y*sin(radian), ty: y-x*sin(radian)-y*cos(radian))
        return transform
}

具体效果


rotate.gif

重点整理

  1. 二维线性变换能实现很多效果,如缩放,旋转等,这些变换使用二维矩阵就够了,而CGAffineTransfrom使用三维矩阵的原因是,二维空间下的线性变换无法实现平移,需要从三维的线性变换来实现二维的平移
  2. 仿射变换都是根据自身坐标系来进行,这个自身坐标系以anchorPoint所指向的点为(0,0),而不是直接使用和其frame一样的坐标系
  3. 灵活应用 a,b,c,d,tx,ty
  4. 了解frame的计算方式,以及transform如何影响frame
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,458评论 6 513
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,030评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,879评论 0 358
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,278评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,296评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,019评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,633评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,541评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,068评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,181评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,318评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,991评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,670评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,183评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,302评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,655评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,327评论 2 358