アフィン変換を使った図形変換について解説します。
CGAffineTransformは図形変換情報をアフィン変換行列形式で保持する構造体です。
頭のCGはAppleの描画系フレームワーク(Core Graphics)の略です。
環境
この記事の情報は次のバージョンで動作確認しています。
【Swift】5.2.4
【iOS】13.6
【macOS】Catalina バージョン 10.15.6
アフィン変換とは?
幾何学の分野で、図形を回転したり引き伸ばしたりする変換をアフィン変換と呼びます。
アフィン変換行列は、図形オブジェクトを回転、拡大縮小、移動、または傾斜させるために使用される3x3の行列です。
CGAffineTransformは図形変換情報をアフィン変換行列形式で保持する構造体です。
この記事では、アフィン変換行列そのものの説明は省略し、CGAffineTransformを使って実現する基本的な図形変更方法を解説します。
図形の平行移動
1 2 3 |
CGAffineTransform(translationX: CGFloat, y: CGFloat) |
図形の変換情報(平行移動)を生成します。
引数translationXにX軸の移動量、引数yにY軸の移動量をCGFloat型で指定します。
既存のPathに図形変換情報を適用するには、applying()メソッドを使います。
使用例
正方形のPath情報に、平行移動の図形変換情報を適用する例です。
X軸、Y軸の移動量をSliderで変更します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
struct ContentView: View { @State private var amountX: CGFloat = 0.0 // X軸の移動量 @State private var amountY: CGFloat = 0.0 // Y軸の移動量 var body: some View { /// 図形変換情報生成(平行移動) let transform = CGAffineTransform(translationX: amountX, y: amountY) /// 図形(path)生成 var path = Path() path.addRect(CGRect(x: 0, y: 0, width: 100, height: 100)) // 正方形の追加 path = path.applying(transform) // 図形変換情報を適用 return VStack(spacing: 20) { /// 図形表示 path .fill(Color.blue) .frame(width: 100, height: 100) .border(Color.red) /// X軸変更変更Slider HStack { Slider(value: $amountX, in: -100...100) Text("\(amountX, specifier: "%.2f")") } .padding(.horizontal) /// Y軸変更変更Slider HStack { Slider(value: $amountY, in: -100...100) Text("\(amountY, specifier: "%.2f")") } .padding(.horizontal) } } } |
図形の拡大・縮小
1 2 3 |
CGAffineTransform(scaleX: CGFloat, y: CGFloat) |
図形の変換情報(拡大・縮小)を生成します。
引数scaleXにX軸の拡大率、引数yにY軸の拡大率をCGFloat型で指定します。
座標(x:0、 y:0)が常に拡大・縮小の起点となります。
使用例
正方形のPath情報に、拡大・縮小の図形変換情報を適用する例です。
X軸、Y軸の拡大率をSliderで変更します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
struct ContentView: View { @State private var scaleX: CGFloat = 1.0 // X軸拡大率 @State private var scaleY: CGFloat = 1.0 // Y軸拡大率 var body: some View { /// 図形変換情報生成(拡大・縮小) let transform = CGAffineTransform(scaleX: scaleX, y: scaleY) /// 図形(path)生成 var path = Path() path.addRect(CGRect(x: 0, y: 0, width: 100, height: 100)) // 正方形の追加 path = path.applying(transform) // 図形変換情報を適用 return VStack(spacing: 20) { /// 図形表示 path .fill(Color.blue) .frame(width: 100, height: 100) .border(Color.red) /// X軸拡大率変更Slider HStack { Slider(value: $scaleX, in: -3...3) Text("\(scaleX, specifier: "%.2f")") } .padding(.horizontal) /// Y軸拡大率変更Slider HStack { Slider(value: $scaleY, in: -3...3) Text("\(scaleY, specifier: "%.2f")") } .padding(.horizontal) } } } |
図形の回転
1 2 3 |
CGAffineTransform(rotationAngle: CGFloat) |
図形の変換情報(回転)を生成します。
引数rotationAngleに回転角度をCGFloat型で指定します。
なお、回転角度はラジアンで指定する必要がありますので、度数法で指定したい場合はAngle構造体を使って変換すると良いでしょう。
拡大・縮小と同様、回転においても座標(x:0、 y:0)が起点となります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
struct ContentView: View { @State private var angle = 0.0 // 回転角度 var body: some View { /// 図形変換情報生成(回転) let transform = CGAffineTransform(rotationAngle: CGFloat(Angle(degrees: angle).radians)) /// 図形(path)生成 var path = Path() path.addRect(CGRect(x: 0, y: 0, width: 100, height: 100)) // 正方形の追加 path = path.applying(transform) // 図形変換情報を適用 return VStack(spacing: 20) { /// 図形表示 path .fill(Color.blue) .frame(width: 100, height: 100) .border(Color.red) /// 角度変更Slider HStack { Slider(value: $angle, in: 0...360) Text("\(angle, specifier: "%.2f")") } .padding(.horizontal) } } } |
図形変換情報を組み合わせる
CGAffineTransformは、次のメソッドで図形変換情報の組み合わせが可能です。
1 2 3 |
concatenating(CGAffineTransform) |
以下は、図形の平行移動+回転+平行移動を組み合わせて、図形の中心を起点に回転させる例です。
アフィン変換による図形の回転は、常に座標(x:0, y:0)が起点になるため、図形の中心を一旦そちらに合わせ、回転後元の戻しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
struct ContentView: View { @State private var angle = 0.0 var body: some View { /// 図形変換情報生成(回転) /// concatenating(組み合わせ)メソッドを使って、「移動+回転+移動(元に戻す)」の変換情報を生成 var transform = CGAffineTransform(translationX: -50, y: -50) transform = transform.concatenating(CGAffineTransform(rotationAngle: CGFloat(Angle(degrees: angle).radians))) transform = transform.concatenating(CGAffineTransform(translationX: 50, y: 50)) var path = Path() path.addRect(CGRect(x: 0, y: 0, width: 100, height: 100)) path = path.applying(transform) return VStack(spacing: 20) { path .fill(Color.blue) .frame(width: 100, height: 100) .border(Color.red) HStack { Slider(value: $angle, in: 0...360) Text("\(angle, specifier: "%.2f")") } .padding(.horizontal) } } } |
アフィン変換の利用例
アフィン変換を利用して、幾何学的な模様を表示するサンプルを紹介します。
できるだけコメントを入れたので、コードを追うことで理解が深まると思います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
struct ContentView: View { @State private var elipses = 1.0 // 楕円形の数 var body: some View { /// 図形変換情報(回転)の生成 /// 表示する楕円形の数によって、回転する角度を変える let rotation = CGAffineTransform(rotationAngle: CGFloat(Angle(degrees: 180.0 / elipses).radians)) /// 図形(path)生成 /// 図形全体を回転させながら、指定された数だけ楕円形を追加する /// 楕円形の中心を支点に回転させる為、中心が(X:0, y:0)なるように追加 /// 最後に図形全体をframeの中心に移動 var path = Path() for _ in 1...Int(elipses) { path.addEllipse(in: CGRect(x: -25, y: -100, width: 50, height: 200)) // 楕円形を追加 path = path.applying(rotation) // 図形を回転 } path = path.applying(CGAffineTransform(translationX: 100, y: 100)) // frameの中心に平行移動 /// Viewの表示 return VStack(spacing: 20) { /// 図形表示 path .stroke(Color.blue) .frame(width: 200, height: 200) /// 楕円形の数変更変更Slider HStack { Slider(value: $elipses, in: 1...100) Text("\(elipses, specifier: "%.2f")") } .padding(.horizontal) } } } |