0
0

CSS と SVG の transform について

Last updated at Posted at 2024-07-18

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}
$$

matrixtransform 属性で扱うことのできる最も一般的な変形関数の表式であり、 rotatescale などの決まった変換操作の関数は全て 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 などの長さの単位を持った値、或いは要素の横幅縦幅に対するパーセント表記が指定できる
  • translatedx, 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} $$

また、 translatetranslate3d は行列表記と次のように対応する。

$$ \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} $$

matrixmatrix3d では 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 は全て単位のない実数値が指定可能である
  • scalekx, 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} $$

また、 scalescale3d は行列表記と次のように対応する。

$$ \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(θ) ×
  • オブジェクトを回転させることができる。いずれも θ で回転角を与えている
  • 関数の違いは回転軸の違いである。 rotaterotateZ は2次元でいえば原点 (0,0) を中心とした回転であり、3次元であれば $z$ 軸に対しての回転である。 rotateX は $x$ 軸に対して、 rotateY は $y$ 軸に対しての回転である。 rotate3d(nx,ny,nz) で与えられるベクトル方向を回転軸とした回転である
  • CSS の rotaterotateZ は等価である
  • 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 \\
& = I - N^2 (1-\cos\theta) - N \sin\theta \\
& = I + N^2 \cdot \sum_{n=1}^\infty \frac{{(-)}^n\theta^{2n}}{(2n)!} + N \cdot \sum_{n=1}^\infty \frac{{(-)}^n\theta^{2n-1}}{(2n-1)!} \\
&= I + \sum_{n=1}^\infty \frac{{(-)}^n}{n!} (N\theta)^n
= \exp(-N\cdot\theta)
\qquad\longleftarrow\quad N^{2n} = N^2 \quad N^{2n-1} = N \\
& = \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} $$

剪断変形

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$ だけ回転させたのと同じ結果が得られる」ということを考えれば明らかであろう。

このように合成したら必ず matrixmatrix3d で表す必要があるわけではない。特に「 rotaterotate を合成して 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} $$

また、 skewXskewY を合成させても 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}$ を求めなくてはならない。

閉じている例: 遠近感

$\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 の実装について気になったら、この記事の内容を思い返せるようにしたい。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0