CSS には transform
というプロパティがあり、 HTML や SVG 要素に対して変形を行うことができる。一方 SVG の要素にも、 transform
属性があり、その要素の変形を行うことができる。
これらの特徴について理解するためには MDN における CSS, SVG それぞれの記事を参照すれば良いのだが、個人的には理解できていない内容や、気になったことがいくつかあった。
そこで、これらの記事の内容と、自力で調べたことを、本記事にまとめていく。
とりわけ、次の点に着目した。
- CSS と SVG の
transform
で指定できる変形関数の違い - それぞれの変形関数の関係性や行列表現
- 合成に対して変形関数が閉じているか
そもそも transform
とは
本記事が説明しようとしている transform
プロパティや属性とは何なのか、最初に説明する。 transform
はよく知っているという方は飛ばして次のセクションに進んでも構わない。
transform
とは HTML や SVG において、画面上に描かれている物体に対して行列変形を行うことができるプロパティや属性である。
例えば次のように HTML を記述したとする。
<img src="img.png" width="100px" height="150px" style="transform:scale(2);" />
style
属性の transform
プロパティに scale(2)
と記されている。これにより画像がその場で2倍の大きさ ( 200px × 300px ) になるはずだ。
SVG においては次のように transform
属性が存在する。
<circle fill="red" cx="0" cy="0" r="50" transform="scale(2)" />
こちらも半径が100の円になるはずだ。
どちらの例でも scale
関数を例として挙げたが、他にも translate(dx,dy)
で平行移動させたり、 rotate(θ)
で回転させたり、 skew(θx,θy)
で剪断変形させることもできる。
平行移動や回転などそれぞれの変換操作を扱う関数の他に、行列を使って直接変換が指定可能な matrix(a,b,c,d,e,f)
というものもある。この場合、次の式に従って元のオブジェクトの座標 $(x,y)$ を $(x',y')$ に写像させている。
$$
\begin{pmatrix}\; x' \;\\\; y' \;\\\; 1 \;\end{pmatrix} =
\begin{pmatrix}\; a & c & e \;\\\; b & d & f \;\\\; 0 & 0 & 1 \;\end{pmatrix}
\begin{pmatrix}\; x \;\\\; y \;\\\; 1 \;\end{pmatrix}
$$
matrix
は transform
属性で扱うことのできる最も一般的な変形関数の表式であり、 rotate
や scale
などの決まった変換操作の関数は全て matrix(..)
を使った行列により表すことができる。
例えば transform(dx,dy)
は matrix(1,0,0,1,dx,dy)
と等価であり、数式だと次のように表される。
$$
\begin{pmatrix}\; x' \;\\\; y' \;\\\; 1 \;\end{pmatrix} =
\begin{pmatrix}\; 1 & 0 & d_x \;\\\; 0 & 1 & d_y \;\\\; 0 & 0 & 1 \;\end{pmatrix}
\begin{pmatrix}\; x \;\\\; y \;\\\; 1 \;\end{pmatrix}
$$
3次元の変換について
CSS の transform
では3次元を扱うことができる。
とはいっても画面は2次元なので、 $z$ 方向は画面に垂直な方向 (画面表向きが $+z$ 方向) で、実際には $z$ 成分の違いはオブジェクトの並び順の違いにしか現れない。
CSS には3次元特有の変形関数 translate3D(dx,dy,dz)
や perspective(d)
などが存在し、これを使って変形させることができる。
また、 matrix
は2次元の行列しか扱うことができないので、3次元の行列を扱うための関数 matrix3d
も用意されている。3次元だからベクトルも4次元になっており、次のようにベクトルを自由に変換できる16成分で記述することになっている。
$
\mathrm{matrix3d}(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p)
\quad\cdots\qquad
$
$$
\begin{pmatrix}\; x' \;\\\; y' \;\\\; z' \;\\\; w' \;\end{pmatrix} =
\begin{pmatrix}\; a & b & c & d \;\\\; e & f & g & h \;\\\; i & j & k & l \;\\\; m & n & o & p \;\end{pmatrix}
\begin{pmatrix}\; x \;\\\; y \;\\\; z \;\\\; w \;\end{pmatrix}
$$
※ $w'$ については perspective(d)
では効いてくるが、他の場所ではあまり使うことはないので、この後の perspective(d)
の説明において合わせて説明する。
複数の変形関数の指定
transform
属性やプロパティはスペース区切りで複数の変換を指定することができる。これは複数の変形操作を右から順に適用させることを意味する。
例えば $\mathrm{matrix}(a,b,c,d,e,f)\;\mathrm{matrix}(a',b',c',d',e',f')$ と指定することは、数式だと次のように表される。
$$
\begin{pmatrix}\; x' \;\\\; y' \;\\\; 1 \;\end{pmatrix} =
\begin{pmatrix}\; a & c & e \;\\\; b & d & f \;\\\; 0 & 0 & 1 \;\end{pmatrix}
\begin{pmatrix}\; a' & c' & e' \;\\\; b' & d' & f' \;\\\; 0 & 0 & 1 \;\end{pmatrix}
\begin{pmatrix}\; x \;\\\; y \;\\\; 1 \;\end{pmatrix}
$$
一般的に2つの変形関数を入れ替えると、全体として変形の仕方は変わってくる。
$$ \begin{aligned}
& \mathrm{matrix}(a,b,c,d,e,f)\;\mathrm{matrix}(a',b',c',d',e',f') \\
& \quad \ne
\mathrm{matrix}(a',b',c',d',e',f')\;\mathrm{matrix}(a,b,c,d,e,f)
\end{aligned} $$
CSS と SVG の transform
の違い
CSS と SVG の transform
がどういうものか説明した。どちらも変形関数を1つ以上指定すれば変形ができ、表記の仕方も似ているので、値をコピペしても使うこともできる場合がある。もちろん、全く同じ値が指定できるわけではなく、幾つか違いが存在する。
私が気づいた限りでは、 CSS と SVG の transform
には次の違いが存在するようである。
- CSS では2次元と3次元の変形関数が存在するが、 SVG には2次元の変形関数しか存在しない
- CSS ではカンマ区切りで引数を渡す必要があるが、 SVG ではカンマ区切りの他にスペース区切りにも対応している
- 長さを指定する際には CSS では
px
などの長さ単位を付した表記を使用しなければならない一方、 SVG ではビューポートを基準とした単位表記のない値しか使用できない - 角度に対しても CSS では
deg
,rad
,grad
,turn
に対応しており、値に適切な単位を付す必要があるが、 SVG ではdeg
のみに対応しており、単位表記をせずに度数法の値だけを指定する必要がある
指定可能な変形関数
次に、 transform
で指定可能な変形関数を紹介していく。
同一名の変形関数でも MDN の記事では CSS と SVG のそれぞれで別のページにまとめているが、ここでは違いを明確にするため、同一の変形関数はまとめて取り扱うことにする。
それぞれの変形関数間の対応関係や、対応する行列表記についても説明する。
平行移動
CSS | SVG | |
---|---|---|
translate(dx,dy) |
◯ | ◯ |
translate3d(dx,dy,dz) |
◯ | × |
translateX(dx) |
◯ | × |
translateY(dy) |
◯ | × |
translateZ(dz) |
◯ | × |
- オブジェクトを平行移動させることができる。 $x$ 方向に
dx
だけ、 $y$ 方向にdy
だけ、 $z$ 方向にdz
だけ移動する - SVG の場合は単位のない実数での表記 (
viewBox
の座標系になる) が必要で、 CSS の場合はpx
などの長さの単位を持った値、或いは要素の横幅縦幅に対するパーセント表記が指定できる -
translate
はdx
,dy
の2つの引数を取るが、1つだけ指定するとdy=0
とみなして $x$ 方向にだけ平行移動する
次の関係が成立することは明らかである。
$$ \begin{aligned}
\mathrm{translate}(d_x,d_y) {}={} & \mathrm{translate3d}(d_x,d_y,0) \\
\mathrm{translate}(d_x) {}={} & \mathrm{translate}(d_x,0) \\
\mathrm{translateX}(d_x) {}={} & \mathrm{translate}(d_x,0) \\
\mathrm{translateY}(d_y) {}={} & \mathrm{translate}(0,d_y) \\
\mathrm{translateZ}(d_z) {}={} & \mathrm{translate3d}(0,0,d_z)
\end{aligned} $$
また、 translate
や translate3d
は行列表記と次のように対応する。
$$ \begin{aligned}
\mathrm{translate}(d_x,d_y) \quad\cdots\quad &
\begin{pmatrix}\; 1 & 0 & d_x \;\\\; 0 & 1 & d_y \;\\\; 0 & 0 & 1 \;\end{pmatrix} \quad\cdots\quad
\mathrm{matrix}(1,0,0,1,d_x,d_y) \\
\mathrm{translate3d}(d_x,d_y,d_z) \quad\cdots\quad &
\begin{pmatrix}\; 1 & 0 & 0 & d_x \;\\\; 0 & 1 & 0 & d_y \;\\\; 0 & 0 & 1 & d_z \;\\\; 0 & 0 & 0 & 1 \;\end{pmatrix}
\end{aligned} $$
matrix
や matrix3d
では CSS でも単位表記なしで指定するが、 px
単位での値を指定することになっている。
拡大,縮小
CSS | SVG | |
---|---|---|
scale(kx,ky) |
◯ | ◯ |
scale3d(kx,ky,kz) |
◯ | × |
scaleX(kx) |
◯ | × |
scaleY(ky) |
◯ | × |
scaleZ(kz) |
◯ | × |
- オブジェクトを拡大、縮小させることができる。 $x$ 方向に
kx
倍、 $y$ 方向にky
倍、 $z$ 方向にkz
倍される - 上記の
kx
,ky
,kz
は全て単位のない実数値が指定可能である -
scale
はkx
,ky
の2つの引数を取るが、1つだけ指定するとkx=ky
とみなして $x$, $y$ 共に同じ倍率だけ拡大縮小される
次の関係が成立することは明らかである。
$$ \begin{aligned}
\mathrm{scale}(k_x,k_y) {}={} & \mathrm{scale3d}(k_x,k_y,1) \\
\mathrm{scale}(k) {}={} & \mathrm{scale}(k,k) \\
\mathrm{scaleX}(k_x) {}={} & \mathrm{scale}(k_x,1) \\
\mathrm{scaleY}(k_y) {}={} & \mathrm{scale}(1,k_y) \\
\mathrm{scaleZ}(k_z) {}={} & \mathrm{scale3d}(1,1,k_z)
\end{aligned} $$
また、 scale
や scale3d
は行列表記と次のように対応する。
$$ \begin{aligned}
\mathrm{scale}(k_x,k_y) \quad\cdots\quad &
\begin{pmatrix}\; k_x & 0 & 0 \;\\\; 0 & k_y & 0 \;\\\; 0 & 0 & 1 \;\end{pmatrix} \quad\cdots\quad
\mathrm{matrix}(k_x,0,0,k_y,0,0) \\
\mathrm{scale3d}(k_x,k_y,k_z) \quad\cdots\quad &
\begin{pmatrix}\; k_x & 0 & 0 & 0 \;\\\; 0 & k_y & 0 & 0 \;\\\; 0 & 0 & k_z & 0 \;\\\; 0 & 0 & 0 & 1 \;\end{pmatrix}
\end{aligned} $$
回転
CSS | SVG | |
---|---|---|
rotate(θ) rotate(θ,rx,ry)
|
◯ | ◯ |
rotate3d(nx,ny,nz,θ) |
◯ | × |
rotateX(θ) |
◯ | × |
rotateY(θ) |
◯ | × |
rotateZ(θ) |
◯ | × |
- オブジェクトを回転させることができる。いずれも
θ
で回転角を与えている - 関数の違いは回転軸の違いである。
rotate
とrotateZ
は2次元でいえば原点(0,0)
を中心とした回転であり、3次元であれば $z$ 軸に対しての回転である。rotateX
は $x$ 軸に対して、rotateY
は $y$ 軸に対しての回転である。rotate3d
は(nx,ny,nz)
で与えられるベクトル方向を回転軸とした回転である - CSS の
rotate
とrotateZ
は等価である - CSS の
rotate
は回転角の引数のみを受け付けるが、 SVG のrotate
では回転の中心座標(rx,ry)
をその後の引数で与えることができる。与えない場合は原点を中心に回転する - CSS の場合は
deg
などの角度の単位を持った値を与え、 SVG の場合は単位のない実数値 (deg
に相当) を与える
次の関係が与えられる。
$$ \begin{aligned}
\mathrm{rotate}(\theta) {}={} & \mathrm{rotateZ}(\theta) \\
\mathrm{rotate}(\theta,r_x,r_y) {}={} & \mathrm{translate}(r_x,r_y) \; \mathrm{rotate}(\theta) \; \mathrm{translate}(-r_x,-r_y) \\
\mathrm{rotateX}(\theta) {}={} & \mathrm{rotate3d}(1,0,0,\theta) \\
\mathrm{rotateY}(\theta) {}={} & \mathrm{rotate3d}(0,1,0,\theta) \\
\mathrm{rotateZ}(\theta) {}={} & \mathrm{rotate3d}(0,0,1,\theta) \\
\mathrm{rotate3d}(n_x,n_y,n_z,\theta) {}={} & \mathrm{rotate3d}(-n_x,-n_y,-n_z,-\theta)
\end{aligned} $$
また、 rotate3d
以外のそれぞれの変形関数は行列表記と次のように対応する。
$$ \begin{aligned}
\mathrm{rotate}(\theta) \quad\cdots\quad &
\begin{pmatrix}\; \cos\theta & -\sin\theta & 0 \;\\\; \sin\theta & \cos\theta & 0 \;\\\; 0 & 0 & 1 \;\end{pmatrix} \quad\cdots\quad
\mathrm{matrix}(\cos\theta,\sin\theta,-\sin\theta,\cos\theta,0,0) \\
\mathrm{rotateX}(\theta) \quad\cdots\quad &
\begin{pmatrix}\; 1 & 0 & 0 & 0 \;\\\; 0 & \cos\theta & -\sin\theta & 0 \;\\\; 0 & \sin\theta & \cos\theta & 0 \;\\\; 0 & 0 & 0 & 1 \;\end{pmatrix} \\
\mathrm{rotateY}(\theta) \quad\cdots\quad &
\begin{pmatrix}\; \cos\theta & 0 & \sin\theta & 0 \;\\\; 0 & 1 & 0 & 0 \;\\\; -\sin\theta & 0 & \cos\theta & 0 \;\\\; 0 & 0 & 0 & 1 \;\end{pmatrix} \\
\mathrm{rotateZ}(\theta) \quad\cdots\quad &
\begin{pmatrix}\; \cos\theta & -\sin\theta & 0 & 0 \;\\\; \sin\theta & \cos\theta & 0 & 0 \;\\\; 0 & 0 & 1 & 0 \;\\\; 0 & 0 & 0 & 1 \;\end{pmatrix} \\
\end{aligned} $$
SVG における回転の中心が与えられた rotate
は行列表記と次のように対応する。
$$
\mathrm{rotate}(\theta,r_x,r_y) \quad\cdots\quad
\begin{pmatrix}\;
\cos\theta & -\sin\theta & r_x - r_x \cos\theta + r_y \sin\theta \;\\\; \sin\theta & \cos\theta & r_y - r_x \sin\theta - r_y \cos\theta \;\\\;
0 & 0 & 1
\;\end{pmatrix}
$$
rotate3d
に関しては、引数 $n_x$, $n_y$, $n_z$ は規格化 ($\; {n_x}^2+{n_y}^2+{n_z}^2 = 1 \;$) されているものとし、次のような行列表記で表される。 (ロドリゲスの回転公式)
$$ \begin{aligned} &
\begin{pmatrix}\; 1 & 0 & 0 & 0 \;\\\; 0 & 1 & 0 & 0 \;\\\; 0 & 0 & 1 & 0 \;\\\; 0 & 0 & 0 & 1 \;\end{pmatrix}
- \begin{pmatrix}\; 1 - {n_x}^2 & - n_x n_y & - n_x n_z & 0 \;\\\; - n_y n_x & 1 - {n_y}^2 & - n_y n_z & 0 \;\\\; - n_z n_x & - n_z n_y & 1 - {n_z}^2 & 0 \;\\\; 0 & 0 & 0 & 0 \;\end{pmatrix} (1-\cos\theta)
- \begin{pmatrix}\; 0 & -n_z & n_y & 0 \;\\\; n_z & 0 & -n_x & 0 \;\\\; -n_y & n_x & 0 & 0 \;\\\; 0 & 0 & 0 & 0 \;\end{pmatrix} \sin\theta \\
& = \exp\left[\; \begin{pmatrix}\; 0 & n_z & -n_y & 0 \;\\\; -n_z & 0 & n_x & 0 \;\\\; n_y & -n_x & 0 & 0 \;\\\; 0 & 0 & 0 & 0 \;\end{pmatrix} \cdot \theta \;\right]
\end{aligned} $$
この計算の詳細はこの下で説明している。
計算の詳細
式中に現れた行列を次のように $I$, $n$, $N$ でおく。
$$\begin{aligned} &
I := \begin{pmatrix}\; 1 & 0 & 0 & 0 \;\\\; 0 & 1 & 0 & 0 \;\\\; 0 & 0 & 1 & 0 \;\\\; 0 & 0 & 0 & 1 \;\end{pmatrix}
\qquad
n := \begin{pmatrix}\; 0 & -n_z & n_y & 0 \;\\\; n_z & 0 & -n_x & 0 \;\\\; -n_y & n_x & 0 & 0 \;\\\; 0 & 0 & 0 & 0 \;\end{pmatrix}
\\ & \qquad\quad
N := \begin{pmatrix}\; 1 - {n_x}^2 & - n_x n_y & - n_x n_z & 0 \;\\\; - n_y n_x & 1 - {n_y}^2 & - n_y n_z & 0 \;\\\; - n_z n_x & - n_z n_y & 1 - {n_z}^2 & 0 \;\\\; 0 & 0 & 0 & 0 \;\end{pmatrix}
\end{aligned} $$
すると、 $n$ と $N$ の間に次のような関係があることがわかるだろう。 ($k$ は正整数)
$$
\left\lbrace\;\begin{aligned}
- n^2 =N^k {}={}& N \\
nN {}={}& n
\end{aligned}\right.
\quad\Longrightarrow\quad
\begin{aligned}
N {}={}& N^k = {\left(-n^2\right)}^k = {(-)}^k n^{2k} \\
n {}={}& n N^{k-1} = {(-)}^{k-1} n^{2k-1}
\end{aligned}
$$
そして rotate3d
は次のように表せるだろう。
$$
\mathrm{rotate3d}(n_x,n_y,n_z,\theta) = I - N (1-\cos\theta) - n \sin\theta
$$
この第2項、第3項を計算すると
$$ \begin{aligned}
- N(1-\cos\theta)
{}={}& - N \cdot \sum_{k=1}^\infty - \frac{{(-)}^k \theta^{2k}}{(2k)!} \\
{}={}& \sum_{k=1}^\infty {(-)}^k n^{2k} \cdot \frac{{(-)}^k \theta^{2k}}{(2k)!} \\
{}={}& \sum_{k=1}^\infty \frac{{(n\theta)}^{2k}}{(2k)!}
= \sum_{k=1}^\infty \frac{{(-n\theta)}^{2k}}{(2k)!} \\
- n \sin\theta
{}={}& - n \cdot \sum_{k=1}^\infty - \frac{{(-)}^k \theta^{2k-1}}{(2k-1)!} \\
{}={}& \sum_{k=1}^\infty {(-)}^{k-1} n^{2k-1} \cdot \frac{{(-)}^k \theta^{2k-1}}{(2k-1)!} \\
{}={}& \sum_{k=1}^\infty - \frac{{(n\theta)}^{2k-1}}{(2k-1)!}
= \sum_{k=1}^\infty \frac{{(-n\theta)}^{2k-1}}{(2k-1)!}
\end{aligned} $$
また、 $I$ については $\displaystyle I =: \frac{{(-n\theta)^0}}{0!} $ だと考えれば、3つの項は同一の式の0次、偶数次、奇数次の和となっている。
$$ \begin{aligned}
\mathrm{rotate3d}(n_x,n_y,n_z,\theta) {}={}& I - N (1-\cos\theta) - n \sin\theta \\
{}={}& \frac{{(-n\theta)^0}}{0!} + \sum_{k=1}^\infty \frac{{(-n\theta)}^{2k}}{(2k)!} + \sum_{k=1}^\infty \frac{{(-n\theta)}^{2k-1}}{(2k-1)!} \\
{}={}& \sum_{n=0}^\infty \frac{{(-n\theta)}^k}{k!}
= \exp(-n\theta)
\end{aligned} $$
上記の $n$, $N$ に具体的な式を代入すれば得られる。
剪断変形
CSS | SVG | |
---|---|---|
skew(θx,θy) |
◯ | × |
skewX(θx) |
◯ | ◯ |
skewY(θy) |
◯ | ◯ |
- オブジェクトに剪断変形を与えることができる。つまり、
θx
やθy
が正だとしたら、オブジェクトの左上や右下を横方向、或いは縦方向に引っ張ってできる形状を与える。その時の傾き度合いをθx
やθy
で与える - CSS の場合は
deg
などの角度の単位を持った値を与え、 SVG の場合は単位のない実数値 (deg
に相当) を与える -
skew
はθx
,θy
の2つの引数を取るが、1つだけ指定するとθy=0
とみなして $x$ 方向にだけ傾ける
次の関係が成立する。
$$ \begin{aligned}
\mathrm{skew}(\theta_x) {}={} & \mathrm{skew}(\theta_x,0) \\
\mathrm{skewX}(\theta_x) {}={} & \mathrm{skew}(\theta_x,0) \\
\mathrm{skewY}(\theta_y) {}={} & \mathrm{skew}(0,\theta_y)
\end{aligned} $$
また、それぞれの関数は次のように行列表記と対応する。
$$ \begin{aligned}
\mathrm{skew}(\theta_x,\theta_y) \quad\cdots\quad &
\begin{pmatrix}\; 1 & \tan\theta_x & 0 \;\\\; \tan\theta_y & 1 & 0 \;\\\; 0 & 0 & 1 \;\end{pmatrix} && \quad\cdots\quad
\mathrm{matrix}(1,\tan\theta_y,\tan\theta_x,1,0,0) \\
\mathrm{skewX}(\theta) \quad\cdots\quad &
\begin{pmatrix}\; 1 & \tan\theta & 0 \;\\\; 0 & 1 & 0 \;\\\; 0 & 0 & 1 \;\end{pmatrix} && \quad\cdots\quad
\mathrm{matrix}(1,0,\tan\theta,1,0,0) \\
\mathrm{skewY}(\theta) \quad\cdots\quad &
\begin{pmatrix}\; 1 & 0 & 0 \;\\\; \tan\theta & 1 & 0 \;\\\; 0 & 0 & 1 \;\end{pmatrix} && \quad\cdots\quad
\mathrm{matrix}(1,\tan\theta,0,1,0,0)
\end{aligned} $$
遠近感
CSS | SVG | |
---|---|---|
perspective(d) |
◯ | × |
- オブジェクトに遠近感をもたらす
- 画面は2次元の面なので通常オブジェクトの $z$ 座標を変えても表示内容にはそのままでは違いが生まれない
-
perspective
ではオブジェクトの $z$ 座標に基づいて遠くにある物体ほど小さく、近くにある物体ほど大きく見えるように $x$, $y$ 座標も含めて調整される
-
- 引数には長さの単位を持った正の値を与える。これは視点と $z=0$ の面との間の距離を表す
行列表記と次のように対応する。
$$
\mathrm{perspective}(d) \quad\cdots\quad
\begin{pmatrix}\; 1 & 0 & 0 & 0 \;\\\; 0 & 1 & 0 & 0 \;\\\; 0 & 0 & 1 & 0 \;\\\; 0 & 0 & -1/d & 1 \;\end{pmatrix}
$$
ここではじめて4行目の非対格成分に $0$ 以外の値が現れた。つまり行列適用後の $w$ 成分が1以外の値になるということだ。そもそも $w$ 座標とは何か。
この $w$ 座標を含む4成分で表す座標系は同次座標系 (Homogeneous Coordinate) と呼ばれる。 $N$ 次元空間を表すのに $N+1$ 次元にすることで、行列の積でアフィン変換を全て表すことができるようになるというのが利点らしい。
行列変換適用後は次のように $w'$ で割ることによって3次元空間での実際の座標が与えられる。
$$
(x',y',z',w') \qquad\longrightarrow\qquad
\left(\frac{x'}{w'},\frac{y'}{w'},\frac{z'}{w'}\right)
$$
因みに、同次座標系の「同次」の意味についてはこちらの説明が面白い。
スペース区切りで複数個の関数が指定されている場合は、全ての関数が適用された後に $w'$ で割る操作が行われる。
transform
プロパティに perspective(d)
のみが含まれているとすると、変換によって座標は次のように変わる。
$$
x' = x \qquad y' = y \qquad z' = z \qquad w' = 1 - \frac{z}{d}
$$
$$
\Longrightarrow\qquad
\frac{x'}{w'} = \frac{x}{1-z/d} \qquad
\frac{y'}{w'} = \frac{y}{1-z/d} \qquad
\frac{z'}{w'} = \frac{z}{1-z/d}
$$
つまり、オブジェクトの大きさは $z$ が $z=d$ に近づくにつれて反比例的に大きくなることがわかる。
行列による指定
CSS | SVG | |
---|---|---|
matrix(6個の引数) |
◯ | ◯ |
matrix3d(16個の引数) |
◯ | × |
- 2次元あるいは3次元の任意のアフィン変換を行うことができる
- CSS であっても全ての引数は単位なしで指定する。長さとしては
px
単位になる
既に示してはいるが、行列との対応関係を改めて以下に示す。
$$ \begin{aligned}
\mathrm{matrix}(a,b,c,d,e,f) \quad\cdots\quad &
\begin{pmatrix}\; a & c & e \;\\\; b & d & f \;\\\; 0 & 0 & 1 \;\end{pmatrix} \\
\mathrm{matrix3d}(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p) \quad\cdots\quad &
\begin{pmatrix}\; a & b & c & d \;\\\; e & f & g & h \;\\\; i & j & k & l \;\\\; m & n & o & p \;\end{pmatrix}
\end{aligned} $$
合成に関して閉じた変形関数
transform
プロパティあるいは属性にスペース区切りで指定された複数個の変形関数は合成させて1つにまとめることができる。
これは行列操作を考えるとわかりやすい。
例えば $
\mathrm{translate}(d_x,d_y) \; \mathrm{scale}(k_x,k_y)
=
\mathrm{matrix}(k_x,0,0,k_y,d_x,d_y)
$ であることは、次の行列操作を考えれば明らかである。
$$ \begin{aligned}
\begin{pmatrix}\; x' \;\\\; y' \;\\\; 1 \;\end{pmatrix}
{}={} &
\begin{pmatrix}\; 1 & 0 & d_x \;\\\; 0 & 1 & d_y \;\\\; 0 & 0 & 1 \;\end{pmatrix}
\begin{pmatrix}\; k_x & 0 & 0 \;\\\; 0 & k_y & 0 \;\\\; 0 & 0 & 1 \;\end{pmatrix}
\begin{pmatrix}\; x \;\\\; y \;\\\; 1 \;\end{pmatrix} \\
{}={} &
\begin{pmatrix}\; k_x & 0 & d_x \;\\\; 0 & k_y & d_y \;\\\; 0 & 0 & 1 \;\end{pmatrix}
\begin{pmatrix}\; x \;\\\; y \;\\\; 1 \;\end{pmatrix} \\
\end{aligned} $$
以上のような行列計算を行うことで、あらゆる変形操作は2次元なら1つの matrix
、3次元なら1つの matrix3d
で表すことができる。
一方で、 $\mathrm{rotate}(\theta_1)\;\mathrm{rotate}(\theta_2) = \mathrm{rotate}(\theta_1+\theta_2)$ のようにまとめられる。
これは真面目に行列に変換して計算しても求められるが、「 $\theta_1$ だけ回転させた後、 $\theta_2$ だけ回転させれば、 $\theta_1+\theta_2$ だけ回転させたのと同じ結果が得られる」ということを考えれば明らかであろう。
このように合成したら必ず matrix
や matrix3d
で表す必要があるわけではない。特に「 rotate
と rotate
を合成して rotate
を得る」というような rotate
に「閉じた合成」ができるかどうかを、それぞれの変形関数に対して調査した。
結論を先に示すと、 skew
以外は全て閉じている。
skew
は CSS には存在するが SVG には存在しないため、結果的に SVG は全ての変換関数が閉じていることになる。 skewX
, skewY
は CSS と SVG の双方に存在し、 skew
が SVG に存在しないのは、意図的なものなのだろうか。
以下では CSS と SVG に含まれている全ての関数に対して閉じているかを調べ、どのようにすれば合成できるのかを示している。
閉じている例: 平行移動, 拡大縮小
これらは丁寧に調べるまでもなく明らかなので省略する。
$$ \begin{aligned}
\mathrm{translate}(d_{x1},d_{y1}) \; \mathrm{translate}(d_{x2},d_{y2})
{}={} & \mathrm{translate}(\;d_{x1}+d_{x2}\;,\;d_{y1}+d_{y2}\;) \\
\mathrm{scale}(k_{x1},k_{y1}) \; \mathrm{scale}(k_{x2},k_{y2})
{}={} & \mathrm{scale}(\;k_{x1}\cdot k_{x2}\;,\;k_{y1}\cdot k_{y2}\;)
\end{aligned} $$
閉じている/閉じていない例: 剪断変形
$\mathrm{skewX}(\theta_1) \; \mathrm{skewX}(\theta_2)$ を行列を使って計算してみよう。
$$
\begin{pmatrix}\; 1 & \tan\theta_1 & 0 \;\\\; 0 & 1 & 0 \;\\\; 0 & 0 & 1 \;\end{pmatrix}
\begin{pmatrix}\; 1 & \tan\theta_2 & 0 \;\\\; 0 & 1 & 0 \;\\\; 0 & 0 & 1 \;\end{pmatrix} =
\begin{pmatrix}\; 1 & \tan\theta_1 + \tan\theta_2 & 0 \;\\\; 0 & 1 & 0 \;\\\; 0 & 0 & 1 \;\end{pmatrix}
$$
角度 $\theta$ を次のように定義すれば、右辺は $\mathrm{skewX}(\theta)$ と表すことができる。
$$
\tan\theta = \tan\theta_1 + \tan\theta_2
$$
$\mathrm{skewY}$ についても同様であり、以上から、 skewX
, skewY
はそれぞれ閉じていることがわかる。
$$ \begin{aligned}
\mathrm{skewX}(\theta_1) \; \mathrm{skewX}(\theta_2)
{}={} & \mathrm{skewX}(\theta) \\
\mathrm{skewY}(\theta_1) \; \mathrm{skewY}(\theta_2)
{}={} & \mathrm{skewY}(\theta)
\end{aligned} $$
一方、 CSS に含まれているものの SVG には含まれていない skew
は、合成に対して閉じていない。
$$ \begin{aligned} &
\begin{pmatrix}\; 1 & \tan\theta_{x1} & 0 \;\\\; \tan\theta_{y1} & 1 & 0 \;\\\; 0 & 0 & 1 \;\end{pmatrix}
\begin{pmatrix}\; 1 & \tan\theta_{x2} & 0 \;\\\; \tan\theta_{y2} & 1 & 0 \;\\\; 0 & 0 & 1 \;\end{pmatrix}
\\ & =
\begin{pmatrix}\; 1 + \tan\theta_{x1} \tan\theta_{y2} & \tan\theta_{x1} + \tan\theta_{x2} & 0 \;\\\; \tan\theta_{y1} + \tan\theta_{y2} & 1 + \tan\theta_{x2} \tan\theta_{y1} & 0 \;\\\; 0 & 0 & 1 \;\end{pmatrix}
\end{aligned} $$
また、 skewX
や skewY
を合成させても skew
になることはない。
$$ \begin{aligned} &
\begin{pmatrix}\; 1 & \tan\theta_x & 0 \;\\\; 0 & 1 & 0 \;\\\; 0 & 0 & 1 \;\end{pmatrix}
\begin{pmatrix}\; 1 & 0 & 0 \;\\\; \tan\theta_y & 1 & 0 \;\\\; 0 & 0 & 1 \;\end{pmatrix}
\\ & =
\begin{pmatrix}\; 1 & \tan\theta_x & 0 \;\\\; \tan\theta_y & 1 + \tan\theta_x \tan\theta_y & 0 \;\\\; 0 & 0 & 1 \;\end{pmatrix}
\end{aligned} $$
閉じている例: 回転
$\mathrm{rotate}(\theta_1)\;\mathrm{rotate}(\theta_2)$ を行列を使って計算してみよう。
$$ \begin{aligned} &
\begin{pmatrix}\; \cos\theta_1 & -\sin\theta_1 & 0 \;\\\; \sin\theta_1 & \cos\theta_1 & 0 \;\\\; 0 & 0 & 1 \;\end{pmatrix}
\begin{pmatrix}\; \cos\theta_2 & -\sin\theta_2 & 0 \;\\\; \sin\theta_2 & \cos\theta_2 & 0 \;\\\; 0 & 0 & 1 \;\end{pmatrix} \\
& = \begin{pmatrix}\;
\mathrm{c}\theta_1 \; \mathrm{c}\theta_2 - \mathrm{s}\theta_1 \; \mathrm{s}\theta_2 &
{} - \mathrm{c}\theta_1 \; \mathrm{s}\theta_2 - \mathrm{s}\theta_1 \; \mathrm{c}\theta_2 &
0 \;\\\;
\mathrm{s}\theta_1 \; \mathrm{c}\theta_2 + \mathrm{c}\theta_1 \; \mathrm{s}\theta_2 &
{} - \mathrm{s}\theta_1 \; \mathrm{s}\theta_2 + \mathrm{c}\theta_1 \; \mathrm{c}\theta_2 &
0 \;\\\;
0 & 0 & 1
\;\end{pmatrix} \\
& =
\begin{pmatrix}\;
\cos(\theta_1+\theta_2) & -\sin(\theta_1+\theta_2) & 0 \;\\\;
\sin(\theta_1+\theta_2) & \cos(\theta_1+\theta_2) & 0 \;\\\;
0 & 0 & 1
\;\end{pmatrix}
\end{aligned} $$
計算によっても $\mathrm{rotate}(\theta_1+\theta_2)$ が得られた。
座標を入れ替えるなどすれば rotateX
, rotateY
, rotateZ
でも同様に成り立つのは明らかであろう。
$$ \begin{alignedat}{4}
\mathrm{rotateX}&(\theta_1) \;& \mathrm{rotateX}&(\theta_2) & {}={} &&
\mathrm{rotateX}&(\; \theta_1+\theta_2 \;) \\
\mathrm{rotateY}&(\theta_1) \;& \mathrm{rotateY}&(\theta_2) & {}={} &&
\mathrm{rotateY}&(\; \theta_1+\theta_2 \;) \\
\mathrm{rotateZ}&(\theta_1) \;& \mathrm{rotateZ}&(\theta_2) & {}={} &&
\mathrm{rotateZ}&(\; \theta_1+\theta_2 \;)
\end{alignedat} $$
回転中心が指定されている場合の rotate
SVG の rotate
では回転中心を与えていた。この場合も行列計算により閉じていることがわかる。
$\mathrm{rotate}(\theta_1,x_1,y_1)\;\mathrm{rotate}(\theta_2,x_2,y_2)$ の計算結果だけ示すと
$$
\begin{pmatrix}\;
\cos(\theta_1+\theta_2) & - \sin(\theta_1+\theta_2) &
x_1 - (x_1-x_2) \; \mathrm{c}_1 + (y_1-y_2) \; \mathrm{s}_1
- x_2 \mathrm{c}_{12} + y_2 \mathrm{s}_{12}
\;\\\;
\sin(\theta_1+\theta_2) & \cos(\theta_1+\theta_2) &
y_1 - (x_1-x_2) \; \mathrm{s}_1 - (y_1-y_2) \; \mathrm{c}_1
- x_2 \mathrm{s}_{12} - y_2 \mathrm{c}_{12}
\;\\\;
0 & 0 & 1
\;\end{pmatrix}
$$
表記を簡単にするため $\mathrm{c}_1 := \cos\theta_1$, $\mathrm{s}_2 := \sin\theta_2$, $\mathrm{c}_{12} := \cos(\theta_1+\theta_2)$ などを導入した。
この行列表記が次の表記に当てはめられれば、合成した変形関数は $\mathrm{rotate}(\; \theta_1+\theta_2 \;,\; x' \;,\; y' \;)$ と表される。
$$
\begin{pmatrix}\;
\cos(\theta_1+\theta_2) & - \sin(\theta_1+\theta_2) &
x' - x' \mathrm{c}_{12} + y' \mathrm{s}_{12}
\;\\\;
\sin(\theta_1+\theta_2) & \cos(\theta_1+\theta_2) &
y' - x' \mathrm{s}_{12} - y' \mathrm{c}_{12}
\;\\\;
0 & 0 & 1
\;\end{pmatrix}
$$
計算すると $x'$, $y'$ はそれぞれ次のように与えられる。
$$ \begin{alignedat}{4}
x' = {} &&
\frac{x_1+x_2}{2} &&
{} - \frac{x_1-x_2}{2} &
\frac{\mathrm{c}_1-\mathrm{c}_2}{1-\mathrm{c}_{12}} &
{} + \frac{y_1-y_2}{2} &
\frac{\mathrm{s}_1+\mathrm{s}_2-\mathrm{s}_{12}}{1-\mathrm{c}_{12}}
\\
y' = {} &&
\frac{y_1+y_2}{2} &&
{} - \frac{x_1-x_2}{2} &
\frac{\mathrm{s}_1+\mathrm{s}_2-\mathrm{s}_{12}}{1-\mathrm{c}_{12}} &
{} - \frac{y_1-y_2}{2} &
\frac{\mathrm{c}_1-\mathrm{c}_2}{1-\mathrm{c}_{12}}
\end{alignedat} $$
$ \mathrm{c}_{12} = 1 $ の場合はこの式では求められない。これは $ \theta_1 + \theta_2 = 0 $ の場合であり、合成したら回転していないことになる場合であるから、回転の中心 $x'$, $y'$ が意味をなさない。適当に $x'=0$, $y'=0$ のようにしたら良いだろう。
rotate3d
CSS に含まれる rotate3d
については計算が大変であるが、一応閉じていることを示すことができる。
例えば
$$
\mathrm{rotate3d}(l_x,l_y,l_z,\theta)\;\mathrm{rotate3d}(m_x,m_y,m_z,\varphi) =
\mathrm{rotate3d}(n_x,n_y,n_z,\psi)
$$
という関係が成り立っているとした場合、合成後の回転軸ベクトル $\boldsymbol{n}$ と回転角 $\psi$ は次の計算により与えられる。
$$ \begin{aligned}
\cos\psi {} = {} & F_1 \\
\boldsymbol{n} \sin\psi {} = {} &
F_2 \; \boldsymbol{l} +
F_3 \; \boldsymbol{m} +
F_4 \; (\boldsymbol{l}\times\boldsymbol{m})
\end{aligned} $$
$\boldsymbol{l}\times\boldsymbol{m}$ は $\boldsymbol{l}$ と $\boldsymbol{m}$ の外積であり、 $\boldsymbol{l}$ と $\boldsymbol{m}$ に対して垂直な単位ベクトルを表す。
また、 $F_1$, $F_2$, $F_3$, $F_4$ はスカラー関数である。2つの角度 $\theta$, $\varphi$ と、 $\boldsymbol{l}$ と $\boldsymbol{m}$ の内積である $\boldsymbol{l}\cdot\boldsymbol{m}$ に依存して次のように与えられる。
$$ \begin{aligned}
F_1(\;\theta,\;\varphi,\;\boldsymbol{l}\cdot\boldsymbol{m}\;) {} := {} &
\frac{1}{2} \Bigl\lbrace\;
(1+\cos\theta)(1+\cos\varphi)
- 2 (\boldsymbol{l}\cdot\boldsymbol{m}) \sin\theta \sin\varphi
+ {(\boldsymbol{l}\cdot\boldsymbol{m})}^2 (1-\cos\theta)(1-\cos\varphi)
\;\Bigr\rbrace - 1 \\
F_2(\;\theta,\;\varphi,\;\boldsymbol{l}\cdot\boldsymbol{m}\;) {} := {} &
\frac{1}{2} \Bigl\lbrace\;
(1+\cos\varphi) \sin\theta
- (\boldsymbol{l}\cdot\boldsymbol{m}) (1-\cos\theta) \sin\varphi
\;\Bigr\rbrace \\
F_3(\;\theta,\;\varphi,\;\boldsymbol{l}\cdot\boldsymbol{m}\;) {} := {} &
\frac{1}{2} \Bigl\lbrace\;
(1+\cos\theta) \sin\varphi
- (\boldsymbol{l}\cdot\boldsymbol{m}) (1-\cos\varphi) \sin\theta
\;\Bigr\rbrace \\
F_4(\;\theta,\;\varphi,\;\boldsymbol{l}\cdot\boldsymbol{m}\;) {} := {} &
\frac{1}{2} \Bigl\lbrace\;
- \sin\theta \sin\varphi
+ (\boldsymbol{l}\cdot\boldsymbol{m}) (1-\cos\theta) (1-\cos\varphi)
\;\Bigr\rbrace
\end{aligned} $$
求めるにあたっては $ \mathrm{rotate3d}(\boldsymbol{n},\psi) = \mathrm{rotate3d}(-\boldsymbol{n},-\psi) $ であることを利用して $ 0 < \psi < \pi $ に限定し、 $\cos\psi$ を求めてから $\psi$ と $\sin\psi$ を求め、 $\sin\psi$ で割って $\boldsymbol{n}$ を求める、という流れになりそうだ。
$ \sin\psi = 0 $ となる $ \psi = 0,\pi $ の場合はこの方法で $\boldsymbol{n}$ が求められない。 $ \psi = 0 $ に関しては回転していないということだから、 $\boldsymbol{n}$ が意味をなさないため問題とならないが、 $ \psi = \pi $ に関しては $\boldsymbol{n}$ が意味をなすので別の計算方法で $\boldsymbol{n}$ を求めなくてはならない。
回転軸の方向が同じ ( $\boldsymbol{l}=\boldsymbol{m}$ ) 場合、 $\boldsymbol{l}\cdot\boldsymbol{m} = 1$ かつ $\boldsymbol{l}\times\boldsymbol{m} = \boldsymbol{0}$ となるから $F_1$, $F_2$, $F_3$, $F_4$ は次の通りになる。
$$
\begin{aligned}
F_1 {}={}& \cos(\theta+\varphi) \\
F_2 {}={}& \frac{1}{2} \sin(\theta+\varphi) + \frac{1}{2}\bigl(\sin\theta-\sin\varphi\bigr) \\
F_3 {}={}& \frac{1}{2} \sin(\theta+\varphi) + \frac{1}{2}\bigl(\sin\varphi-\sin\theta\bigr) \\
F_4 {}={}& \frac{1}{2} \Bigl( \cos(\theta+\varphi) - \cos\theta - \cos\varphi + 1 \Bigr)
\end{aligned}
$$
故に
$$
\cos\psi = \cos(\theta+\varphi)
\qquad
\boldsymbol{n} \sin\psi = \boldsymbol{l} \sin(\theta+\varphi) = \boldsymbol{m} \sin(\theta+\varphi)
$$
となるから、回転軸は変わらない ( $\boldsymbol{n} = \boldsymbol{l} = \boldsymbol{m}$ ) で、単純な角度の足し算 ( $\psi = \theta + \varphi$ ) になっていることがわかる。
閉じている例: 遠近感
$\mathrm{perspective}(d_1)\;\mathrm{perspective}(d_2)$ を行列を使って計算してみよう。
$$
\begin{pmatrix}\; 1 & 0 & 0 & 0 \;\\\; 0 & 1 & 0 & 0 \;\\\; 0 & 0 & 1 & 0 \;\\\; 0 & 0 & -1/d_1 & 1 \;\end{pmatrix}
\begin{pmatrix}\; 1 & 0 & 0 & 0 \;\\\; 0 & 1 & 0 & 0 \;\\\; 0 & 0 & 1 & 0 \;\\\; 0 & 0 & -1/d_2 & 1 \;\end{pmatrix} =
\begin{pmatrix}\; 1 & 0 & 0 & 0 \;\\\; 0 & 1 & 0 & 0 \;\\\; 0 & 0 & 1 & 0 \;\\\; 0 & 0 & - 1/d_1 - 1/d_2 & 1 \;\end{pmatrix}
$$
長さ $d$ を次のように表すことで、右辺は $\mathrm{perspective}(d)$ と表すことができる。
$$
\frac{1}{d} = \frac{1}{d_1} + \frac{1}{d_2}
$$
まとめ
本記事では CSS の transform
プロパティや SVG の transform
属性について、2者の違いや、比較することで現れる変わった特徴についてまとめた。
HTML や SVG の記述で transform
を指定する場合や、 transform
の実装について気になったら、この記事の内容を思い返せるようにしたい。