.NETでは座標のアフィン変換用にMatrixクラス(名前空間:System.Drawing.Drawing2D)が用意されています。
しかしながら、やっかいな事に、私の思う普通のアフィン変換の行列の表現が行と列が逆(転置されている)だし、行列の掛ける順番も逆になります。
つまり、私の思う普通のアフィン変換の行列は変換前の座標が\(\left( x,\quad y \right) \)、変換後の座標が\(\left( { x }^{ ‘ },\quad { y }^{ ‘ } \right) \)だとすると、
【普通のアフィン変換】
$$\left( \begin{matrix} { x }^{ ‘ } \\ { y }^{ ‘ } \\ 1 \end{matrix} \right) =\left( \begin{matrix} a & b & c \\ d & e & f \\ 0 & 0 & 1 \end{matrix} \right) \left( \begin{matrix} x \\ y \\ 1 \end{matrix} \right) $$
【マイクロソフトのアフィン変換】
$$\left( \begin{matrix} { x }^{ ‘ } & { y }^{ ‘ } & 1 \end{matrix} \right) =\left( \begin{matrix} x & y & 1 \end{matrix} \right) \left( \begin{matrix} a & d & 0 \\ b & e & 0 \\ c & f & 1 \end{matrix} \right) $$
となります。
さらにアフィン変換を行列で表現するときに、拡大縮小、回転、移動を行列で連続的に計算するときは、一般的なアフィン変換では行列の左側から掛けていきますが、マイクロソフト仕様では行列の右側から掛けてきます。
つまり、拡大縮小、回転、移動の行列をそれぞれ、S、R, T とするとし、拡大縮小S→回転R→移動Tの順番でアフィン変換をする場合、アフィン変換行列は
【普通のアフィン変換】
$$\left( \begin{matrix} { x }^{ ‘ } \\ { y }^{ ‘ } \\ 1 \end{matrix} \right) =TRS\left( \begin{matrix} x \\ y \\ 1 \end{matrix} \right)$$
【マイクロソフトのアフィン変換】
$$\left( \begin{matrix} { x }^{ ‘ } & { y }^{ ‘ } & 1 \end{matrix} \right) =\left( \begin{matrix} x & y & 1 \end{matrix} \right) SRT$$
のようになります。
さらに厄介なのが、アフィン変換行列にさらに変換行列を掛け合わせるメソッドが用意されていて、
拡大縮小:Scaleメソッド
回転:Rotateメソッド
移動:Translateメソッド
が用意されているのですが、例えば下記のようなコード
var mat = new Matrix(
1, 2,
3, 4,
5, 6
);
mat.Scale(7, 8);
を書くと、内部の計算は
$$\left( \begin{matrix} 7 & 0 & 0 \\ 0 & 8 & 0 \\ 0 & 0 & 1 \end{matrix} \right) \left( \begin{matrix} 1 & 2 & 0 \\ 3 & 4 & 0 \\ 5 & 6 & 1 \end{matrix} \right) =\left( \begin{matrix} 7 & 14 & 0 \\ 24 & 32 & 0 \\ 5 & 6 & 1 \end{matrix} \right) $$
となっています。
ここで問題なのが、最初にアフィン変換の行列はマイクロソフト仕様では行列の右側から掛けると言いましたが、単に
mat.Scale(7, 8);
と書くと、行列の左側から行列を掛けてしまいます。
これだと計算が合わないので、
mat.Scale(7, 8, MatrixOrder.Append);
と書くことで、
$$\left( \begin{matrix} 1 & 2 & 0 \\ 3 & 4 & 0 \\ 5 & 6 & 1 \end{matrix} \right) \left( \begin{matrix} 7 & 0 & 0 \\ 0 & 8 & 0 \\ 0 & 0 & 1 \end{matrix} \right) =\left( \begin{matrix} 7 & 16 & 0 \\ 21 & 32 & 0 \\ 35 & 48 & 1 \end{matrix} \right)$$
となります。
つまり、実質的にScaleメソッド、Rotateメソッド、Translateメソッドは必ずMatrixOrder.Appendを指定する必要があります。
私は、この事にハマったのですが、これさえ理解できれば
指定した点周りの回転:RotateAtメソッド
逆行列:Invertメソッド
なども用意されているので、アフィン変換をする分には使えなくは無い感じです。
座標の値をアフィン変換行列で変換した後の座標を求めるには TransformPointsメソッド を用います。
一連のソースコードは
var mat = new Matrix(
1, 2,
3, 4,
5, 6,
);
mat.Scale(7, 8, MatrixOrder.Append);
var poi = new Point[]{ new Point(10, 20), new Point(30, 40) };
mat.TransformPoints( poi );
とすると、
(10, 20) → (525, 848)
(30, 40) → (1085, 1808)
と変換されます。
ただ、さらに残念なのが、アフィン変換で用いられる行列は
$$\left( \begin{matrix} { m }_{ 11 } & { m }_{ 12 } & 0 \\ { m }_{ 21 } & { m }_{ 22 } & 0 \\ { d }_{ x } & { d }_{ y } & 1 \end{matrix} \right) $$
となり、3列目の値はアフィン変換では用いられないので、設定することはできません。
というのが、マイクロソフトの仕様なのですが、設定できればもう少し汎用的に使えたのに...
ここで、わざわざマイクロソフト仕様と書いたのは、他にもDirect3Dでも同様の計算となるためです。
Direct3Dに近いOpenGLでは、ここで言っている一般的なアフィン変換となるので、混同しないように注意してください。
【参考情報】
Matrixクラス:https://msdn.microsoft.com/ja-jp/library/system.drawing.drawing2d.matrix(v=vs.110).aspx
変換の行列表現:https://msdn.microsoft.com/ja-jp/library/8667dchf(v=vs.110).aspx
一般的なアフィン変換:https://imagingsolution.net/imaging/affine-transformation/
←C#へ戻る