Help us understand the problem. What is going on with this article?

CGAffineTransformを知る

はじめに

SwiftにはCGAffineTransformという構造体が存在します。2Dグラフィックの描画に使われる、アフィン変換を表します。

アフィン変換を知らなくても、変更したい値を渡せばスケーリング、回転、移動を行うCGAffineTransformを作成することができて、デフォルトにリセットできる機能があります。

CGAffineTransformはUIViewに存在するtransformというPropertyを用いて、Viewに対して変更を加えます。

var transform: CGAffineTransform { get set }

これはアフィン変換を知らないけど、CGAffineTransformを使っていた自分が、CGAffineTransformの仕組みを理解するための記事になります。

アフィン変換とは

幾何学の分野で、ある図形を回転させたり引き延ばしたりする変換をアフィン変換と呼ぶ。
もう少しきちんと説明すると、「アフィン変換とは平行移動と線形変換を組み合わせた変換」のこと。

具体的には、線形変換(拡大縮小、剪断、回転)、平行移動があり、これらの組み合わせで表現されます。

equation01_2x_fabc9070-1967-4d6f-a086-17ab5fcfef6d.png

CGAffineTransformで使用するアフィン変換は、上記の3行3列の行列式で表されます。

3列目は常に(0,0,1)ですので、CGAffineTransformのデータ構造には最初の2列のみの値が含まれます。

ここからはframeがCGRect(x: 0, y: 0, width: 50, height: 50)のUIViewに対して、どうやって計算しているかを見ていきます。

スクリーンショット 2019-12-21 16.33.36.png

・本来はSwiftにはanchorPointというPropertyで回転など幾何学的変更における中心点が決まっていて、デフォルトでは中央なのですが、今回は左上を設定しています。

rotation

CGAffineTransformでは

init(rotationAngle angle: CGFloat)

となっています、angleをラジアンで指定すると

iOSでは、正の値は反時計回りの回転を指定し、負の値は時計回りの回転を指定します。
macOSでは、正の値は時計回りの回転を指定し、負の値は反時計回りの回転を指定します。

angle = 40を指定した場合

スクリーンショット 2019-12-21 23.56.00.png

このようにcenterを軸に40度反時計回りに回転します

回転するためのアフィン変換の行列はこのようになっており、受け取ったangleを元にsin,cosを割り出し、各値が決定します。

equation10_2x_37c5a3fe-695a-4a02-983a-8e807267ffc0 (1).png

計算としては、angleを受け取ったことによって決定した上記の値を回転の計算式に当てはめます。

変換後のx座標 = x座標 x cosθ - y座標 x sinθ + tx
変換後のy座標 = x座標 x sinθ + y座標 x cosθ + ty

を行なっています。

上記の式を元に各座標を求めることができます。

scale

CGAffineTransformでは

init(scaleX sx: CGFloat, y sy: CGFloat)

となっています、scaleX, Yを指定するとViewはそれぞれx,y方向に指定したScaleの倍率分拡大されます。

sx: 2, sy: 1を指定した場合

スクリーンショット 2019-12-22 2.55.03.png

このように指定した方向に値の倍率分拡大します。

スケーリングするためのアフィン変換の行列はこのようになっており、受け取ったscaleX, Yを元に各値が決定します。

equation08_2x_1294061e-22f0-48f7-962a-51d31c17614f.png

計算としては、受け取ったscale値をそのまま座標に乗算します

変換後のx座標 = x座標 x ScaleX
変換後のy座標 = y座標 x ScaleY

を行なっています。

上記の式を元に各座標を求めることができます。

translation

CGAffineTransformでは

init(translationX tx: CGFloat, y ty: CGFloat)

となっています、translationX,Yを指定するとViewはそれぞれx,y方向に指定したtranslationの分移動します。
tx: 50, ty: 30を指定した場合

スクリーンショット 2019-12-22 3.14.50.png

このように指定した方向に値の分移動します。

スケーリングするためのアフィン変換の行列はこのようになっており、受け取ったtranslationX,Yを元に各値が決定します。

equation06_2x_7bc0c515-45dd-4adf-a3f1-426c6ef109f1.png

計算としては、受け取ったtranslation値をそのまま座標に加算します

変換後のx座標 = x座標 + translationX
変換後のy座標 = y座標 + translationY

上記の式を元に各座標を求めることができます。

おまけ

CGAffineTransformには

init(a: CGFloat, b: CGFloat, c: CGFloat, d: CGFloat, tx: CGFloat, ty: CGFloat)

というinitが存在して、今までのいずれかのアフィン変換の行列を指定してあげれば、これからでも操作を行うことが可能です。

試しに40度の回転をしてみます。回転の行列はこうなので、

equation10_2x_37c5a3fe-695a-4a02-983a-8e807267ffc0 (1).png

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var affineView: UIView! {
        didSet {
            affineView.layer.anchorPoint = .init(x: 0, y: 0)
            affineView.frame = .init(x: 0, y: 0, width: 50, height: 50)

            let angle: CGFloat = 40.0

            let radian = (angle * (CGFloat.pi / 180))

            let affineSin = sin(radian)
            let affineCos = cos(radian)

            let affine = CGAffineTransform(a: affineCos,
                                           b: affineSin,
                                           c: -affineSin,
                                           d: affineCos,
                                           tx: 0,
                                           ty: 0)

            affineView.transform = affine
        }
    }


    override func viewDidLoad() {
        super.viewDidLoad()

    }
}


このように同じ行列に指定してあげると、この方法でもinit(rotationAngle angle: CGFloat)と結果を得ることができます。

最後に

本来はデフォルトである、中心を軸にした説明を行っていないので、今後加筆していきたいと思います。

ありがとうございました。

参考リンク

CGAffineTransform

アフィン変換とは

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした