gradientTransform 属性を消す方法

SVG においてグラデーションを表現する要素 `linearGradient` と `radialGradient` では、グラデーションを変形の仕方を記述するための `gradientTransform` という属性が指定可能である。
この `gradientTransform` を指定して作られたグラデーションを、 `gradientTransform` を使わないグラデーションに書き換えることが可能かどうかを説明し、可能であればその計算方法を説明する。

## SVG のグラデーションの基本

SVG ではグラデーションを表現するために [`linearGradient` 要素](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/linearGradient)と [`radialGradient` 要素](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/radialGradient)が用意されている。

### `linearGradient` について

`linearGradient` は直線的なグラデーションを作る。

グラデーションの形状を定めるパラメータとして `linearGradient` には次のような固有の属性が存在する。

| 属性 | 意味 |
|:--:|:--|
| [`x1`](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/x1), [`y1`](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/y1) | 開始点の座標 |
| [`x2`](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/x2), [`y2`](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/y2) | 終了点の座標 |

グラデーションは 開始点 → 終了点 のベクトル方向に描かれる。

![linearGradient の概念図](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/386083/bc05c34a-b775-2d41-2e8a-4ec1c4cafed2.png "linearGradient の概念図")

### `radialGradient` について

`radialGradient` は同心円上に広がるグラデーションを作る。

グラデーションの形状を定めるパラメータとして `radialGradient` には次のような固有の属性が存在する。

| 属性 | 意味 |
|:--:|:--|
| [`r`](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/r), [`cx`](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/cx), [`cy`](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/cy) | 終了円の中心座標と半径 |
| [`fr`](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/fr), [`fx`](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/fx), [`fy`](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/fy) | 開始円の中心座標と半径 (オプション) |

開始円上の点から、終了円上の点に向かう方向へ
グラデーションが描かれる。
(開始円、終了円なる呼び方があるのかどうかは不明)

![radialGradient の概念図](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/386083/19ce6449-9304-1021-1af5-bb1a8ad3399d.png "radialGradient の概念図")

上記の画像だと開始円と終了円の中心は一致しているが、開始円と終了円の中心がずれている場合もあるので、挙動を考えておく必要がある。
考えるにあたって[W3C のドキュメント](https://svgwg.org/svg2-draft/pservers.html#RadialGradientElementFXAttribute)が参考になる。 (以下このページからの引用)

> ![W3C のページから引用した画像](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/386083/8abdd523-6eaf-db9e-94a2-59daab813d5c.png "W3C のページから引用した画像")
>
> The dashed lines show two gradient vectors. Vectors connect corresponding points on the inner and outer most circles.

どうやらグラデーションの向きを定めるベクトル (Vectors) は2つの円の対応する点 (corresponding points) を結んでいるということなので、それぞれ円の中心から同じ方向にある点同士を結ぶ向きにグラデーションが進むと考えられる。

### グラデーションの変形

両方のグラデーション要素で共通して指定できる属性として [`gradientTransform`](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/gradientTransform) がある。これによりグラデーションの形状を平行移動や回転、拡大縮小などにより変化させることができる。

この属性で変形の仕方を指定する方法は、 `path` や `rect`, `image` などの要素を変形させるための [`transform` 属性](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/transform)と同じである。具体的な指定の仕方は、私が前に書いた [CSS と SVG の transform について](https://qiita.com/smileyFace/items/e939dc06e0f0e9c3bdb2) にも書かれている。

## ここでやりたいこと

ここでは、 (例えば `linearGradient` であれば) `x1`, `y1`, `x2`, `y2` のような必須のパラメタ属性の他に `gradientTransform` も合わせて指定されたグラデーションが存在していたときに、出来上がるグラデーション自体は全く変えずに、 `x1`, `y1`, `x2`, `y2` の値をうまく変えることにより、 `gradientTransform` を**指定せず**に構成することが可能かどうかを考えたい。
つまり `gradientTransform` の変形を `x1`, `y1`, `x2`, `y2` といった属性に「取り込むこと」が可能かどうかを調べたい。

```xml
<!-- こんな感じのグラデーションを -->
<linearGradient
  x1="63.33" y1="84.03" x2="241.67" y2="84.03"
  gradientTransform="matrix(1,0,0,-1,-4.61,261.08)"
  gradientUnits="userSpaceOnUse">
    <stop offset="0" stop-color="#0c59a4" />
    <stop offset="1" stop-color="#114a8b" />
</linearGradient>

<!-- こうしたい -->
<linearGradient
  x1="??" y1="??" x2="??" y2="??"
  gradientUnits="userSpaceOnUse">
    <!-- gradientTransform が消えている -->
    <stop offset="0" stop-color="#0c59a4" />
    <stop offset="1" stop-color="#114a8b" />
</linearGradient>

<!-- ?? に入る値が何か知りたい -->
```

他の要素に対して `transform` 属性が指定されている場合もそうだが、 `gradientTransform` 属性の指定されたグラデーションが存在しているだけで、 SVG コードは煩雑になりやすい。必須のパラメータに取り込むことができるのであれば単なる冗長表現なので、可能な限り取り除きたい。
また、 SVG を人間が見るにあたって、 `transform` などで変形されていると、どんなものになっているか分かりづらくなってしまう。なるべく単純に表現して分かりやすくしたいのだ。

## 条件設定

### 座標系について

両方のグラデーション要素には [`gradientUnits`](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/gradientUnits) という属性が用意されている。これは座標の決め方を指定する属性であり、 `objectBoundingBox` と `userSpaceOnUse` の2つが指定できる。

* `objectBoundingBox` では、グラデーションを適用するオブジェクトを取り囲む長方形である bounding box を考えた際に、左上角の座標を $(0,0)$, 右下角の座標を $(1,1)$ とする座標系である
* `userSpaceOnUse` でグラデーションを適用するオブジェクトで位置を定める際に使う座標系 (ビューボックス) をそのまま使用する

以下の議論では、おそらくどちらの座標系でも問題なく適用できるはずではあるが、 `userSpaceOnUse` を想定して話を進める。

### 変換関数について

`gradientTransform` では、 `transform()` や `rotate()`, `matrix()` などの変換関数を1つ以上指定することができるが、全て単一の `matrix()` で書き表すことができるため、取り込む変換関数は $\mathrm{matrix}(a,b,c,d,e,f)$ の形式になっているものとする。

## `linearGradient` における変形関数の取り込み

まずは `linearGradient` 要素において `gradientTransform` 属性を取り込む計算を考えてみる。

### 思い込み

`gradientTransform` を取り込む方法として、特に何も考えていないと次のように発想してしまうかもしれない。

> `x1`, `y1` 属性の定める座標 $(x_1,y_1)$ に対して `gradientTransform` の表す行列 $T$ を掛けて得られる座標 $(x_1',y_1')$ を新たな `x1`, `y1` 属性の値にして、 `x2`, `y2` についても同様に計算して書き換えれば求められるのではないか。

自分も前はこのように思っていたが、実際にこの方法で計算して取りこんでみると、結構グラデーションがずれてしまうことがわかる。ということでこれは正しくないのだ。

### 原理に立ち返ってみる

安直な発想では求められないことが分かったので、直線的なグラデーションの描かれ方を考えてみることで、正しい計算式を求めることにしよう。

下に概念図を示している。開始点 $P_1(x_1,y_1)$ と終了点 $P_2(x_2,y_2)$ を結ぶ線の方向にグラデーションが進む。
グラデーションはこの線上だけでなく、面上に作られるので、この線に垂直な線上は、グラデーションの進み具合が同じで、故に色も同じとなる点の集合である。

![概念図](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/386083/76575fdc-a8f1-6ee8-5d55-da86d0472e4e.png "概念図")

領域上の任意の点 $P(x,y)$ の色は、この線に下ろした垂線の足 $P_\perp(x_\perp,y_\perp)$ と同じである。

`stop` 要素の `offset` 属性のように、グラデーション量の進み度合いを表す量 $p$ を考える。開始点 $P_1$ と終了点 $P_2$ では次の値になる。

| 点 | 値 |
|:--:|:--:|
| 開始点 $P_1$ | $p=0$ |
| 終了点 $P_2$ | $p=1$ |

すると、任意の点 $P(x,y)$ における $p$ は次のように計算される。

$$ \begin{aligned}
	p = \frac
	{\Bigl|\overrightarrow{P_1 P_\perp}\Bigr|}
	{\Bigl|\overrightarrow{P_1 P_2}\Bigr|}
	& {}={} \frac
	{\overrightarrow{P_1 P}\cdot\overrightarrow{P_1 P_2}}
	{{\Bigl|\overrightarrow{P_1 P_2}\Bigr|}^2}
	\\\\
	& {}={}
	\frac
	{(x-x_1)(x_2-x_1)+(y-y_1)(y_2-y_1)}
	{{(x_2-x_1)}^2+{(y_2-y_1)}^2}
	\\\\
	& {}={}
	\frac{X(x-x_1)+Y(y-y_1)}{X^2+Y^2}
\end{aligned} $$

後で $p$ の式を扱いやすくするために、 $ X := x_2 - x_1 $ と $ Y := y_2 - y_1 $ を導入した。

レンダラーの実装を見ていないので知らないが、おそらくこの値をもとに、その点での色が決定されているものと考えられる。

因みに、下の図に示したように、グラデーションの方向に垂直な青色の線に沿って、開始点と終了点を同じだけ移動させた、破線のような開始点と終了点の組み合わせでは、どれであっても同じグラデーションになる。
つまり、同じグラデーションになる開始点と終了点の選び方が無数に存在する。

![無数の候補](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/386083/f795cef5-4515-f26f-7784-17bc9eb0c856.png "無数の候補")

### `gradientTransform` による変形とは

`gradientTransform` は、こうしてできるグラデーションを変換関数 $\mathrm{matrix}(a,b,c,d,e,f)$ に従って形を変えてしまう。このとき、点 $P(x,y)$ が点 $P'(x',y')$ に写像されるものとすると、次のように表される。

$$ \begin{aligned}
	\begin{bmatrix}\\; x' \\;\\\\\\; y' \\;\end{bmatrix}
	& {}={}
	\begin{bmatrix}\\; a & c \\;\\\\\\; b & d \\;\end{bmatrix}
	\begin{bmatrix}\\; x \\;\\\\\\; y \\;\end{bmatrix} +
	\begin{bmatrix}\\; e \\;\\\\\\; f \\;\end{bmatrix}
	\\\\\\\\ \iff \quad
	\begin{bmatrix}\\; x \\;\\\\\\; y \\;\end{bmatrix}
	& {}={}
	{\begin{bmatrix}\\; a & c \\;\\\\\\; b & d \\;\end{bmatrix}}^{-1}
	\begin{bmatrix}\\; x' - e \\;\\\\\\; y' - f \\;\end{bmatrix}
	\\\\
	& {}=:{}
	\begin{bmatrix}\\; a' & c' \\;\\\\\\; b' & d' \\;\end{bmatrix}
	\begin{bmatrix}\\; x' \\;\\\\\\; y' \\;\end{bmatrix} +
	\begin{bmatrix}\\; e' \\;\\\\\\; f' \\;\end{bmatrix}
\end{aligned} $$

但し、行列 $\begin{bmatrix}\\; a & c \\;\\\\\\; b & d \\;\end{bmatrix}$ は逆行列が定義できるものとする。 ( $ ad - bc \ne 0 $ )

$a'$, $b'$, $c'$, $d'$, $e'$, $f'$ は $a$, $b$, $c$, $d$, $e$, $f$ を用いて次のように表される。

$$
\begin{aligned}
	a' {}={} & \frac{d}{ad-bc} &
	c' {}={} & \frac{-c}{ad-bc} &
	e' {}={} & - ( a'e + c'f ) \\\\
	b' {}={} & \frac{-b}{ad-bc} &
	d' {}={} & \frac{a}{ad-bc} &
	f' {}={} & - ( b'e + d'f )
\end{aligned}
$$

グラデーションが `gradientTransform` により変形された状態とは、グラデーションの進み度合い $p$ が次のように $(x',y')$ を使って表せることをいう。

$$ \begin{aligned}
	p & {}={} \frac{X(x-x_1)+Y(y-y_1)}{X^2+Y^2} \\\\
	& {}={} \frac{X(a'x'+c'y'+e'-x_1)+Y(b'x'+d'y'+f'-y_1)}{X^2+Y^2}
\end{aligned} $$

そして、 `gradientTransform` を取り込んだ開始点 $P'_1(x'_1,y'_1)$ と終了点 $P'_2(x'_2,y'_2)$ では次のように $p$ が表されるはずだ。

$$
p = \frac{X'(x'-x'_1)+Y'(y'-y'_1)}{{X'}^2+{Y'}^2}
$$

$X$, $Y$ に合わせて $ X' := x'_2 - x'_1 $ , $ Y' := y'_2 - y'_1 $ を導入した。

つまり、上記2つの $p$ の式が任意の $(x',y')$ に対して等しくなるように $x'$, $y'$ に関して係数比較を行い、 $x'_1$, $y'_1$, $x'_2$, $y'_2$ が $x_1$, $y_1$, $x_2$, $y_2$ を用いて表されることが「取り込む」という操作に相当する。

### 取り込んだ開始点と終了点を求める

以上のように方針が立てられたので、実際に計算を進めて、新しい2点のパラメタ $x'_1,y'_1,x'_2,y'_2$ を求めていく。

2つの $p$ に関する式から $x'$ の項の係数、 $y'$ の項の係数、定数項の等式を作ると

$$ \begin{aligned}
	\frac{X'}{{X'}^2+{Y'}^2} {}={} & \frac{a'X + b'Y}{X^2+Y^2} \\\\
	\frac{Y'}{{X'}^2+{Y'}^2} {}={} & \frac{c'X + d'Y}{X^2+Y^2} \\\\
	\frac{- x'_1X' - y'_1Y'}{{X'}^2+{Y'}^2} {}={} & \frac{(e'-x_1)X + (f'-y_1)Y}{X^2+Y^2}
\end{aligned} $$

1行目と2行目の式から、 ${X'}^2+{Y'}^2$ と $X^2+Y^2$ の間の関係式が得られる。
$$
{X'}^2+{Y'}^2 = \frac{{\left(X^2+Y^2\right)}^2}{{\left(a'X+b'Y\right)}^2 + {\left(c'X+d'Y\right)}^2}
$$

これと、上記3つの式を用いて $x'_1$, $y'_1$, $x'_2$, $y'_2$ の間の関係式を計算すると次のようになる。

$$
x'_2 - x'_1 = \alpha
\quad
y'_2 - y'_1 = \beta
\quad
\alpha x'_1 + \beta y'_1 = \gamma
$$

$\alpha$, $\beta$, $\gamma$ は式を見やすくするために導入した定数である。 (具体的な表式は後述)

4つの変数に対して3つの式から構成されるので、全ての変数を一意に決めることできない。

取り込む前において開始点 $(x_1,y_1)$ と終了点 $(x_2,y_2)$ の選び方は無数に存在していると言ったが、これは取り込んだ後においても同様で、式が足りず一意に決められないということは $x'_1$, $y'_1$, $x'_2$, $y'_2$ の選び方が無数に存在していることを示唆している。

$ x'_2 - x'_1 = \alpha $ と $ y'_2 - y'_1 = \beta $ は選び方によらず、開始点と終了点の相対的な位置関係が常に変化しないことを表している。また、 $ \alpha x'_1 + \beta y'_1 = \gamma $ は開始点がこの式の示す直線上から選ばれることを意味している。

先ほど導入した定数 $\alpha$, $\beta$, $\gamma$ は次のように求められる。

$$ \begin{aligned}
	\alpha {}:={} & \frac{a'X+b'Y}{{\left(a'X+b'Y\right)}^2+{\left(c'X+d'Y\right)}^2} (X^2+Y^2)
	\\\\
	\beta {}:={} & \frac{c'X+d'Y}{{\left(a'X+b'Y\right)}^2+{\left(c'X+d'Y\right)}^2} (X^2+Y^2)
	\\\\
	\gamma {}:={} & \frac{(x_1-e')X + (y_1-f')Y}{{\left(a'X+b'Y\right)}^2+{\left(c'X+d'Y\right)}^2} (X^2+Y^2)
\end{aligned} $$

### 1つ選び出す

先ほど述べたように、 `gradientTransform` を取り込んだ開始点と終了点の座標の選び方は無数に存在している。
しかし我々の目的は、 `gradientTransform` を取り込んだ開始点と終了点の座標を求めることであるから、何かしらの指標に基づいてどこか1点に選ばなければならない。
ここでは次の指標で選ぶことにする。

> 直線 $\alpha x' + \beta y' = \gamma$ 上の点のうち、原点 $(0,0)$ から最も近い位置を開始点として選ぶ

[点と直線の距離公式](https://ja.wikipedia.org/wiki/点と直線の距離)から、直線 $ \alpha x' + \beta y' = \gamma $ の原点に最も近い位置の座標 $(x'_1,y'_1)$ は次のように与えられる。

$$
x'_1 = \frac{\alpha \gamma}{\alpha^2 + \beta^2}
\qquad
y'_1 = \frac{\beta \gamma}{\alpha^2 + \beta^2}
$$

![座標の選定の概略図](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/386083/cce6cd8f-9121-5ba4-16ce-cb02240b1c5a.png "座標の選定の概略図")

$\alpha$, $\beta$, $\gamma$ の具体的な値を代入して、 $ x'_2 - x'_1 = \alpha $ と $ y'_2 - y'_1 = \beta $ も用いて、開始点と終了点の座標を求めると次のようになった。

$$ \begin{aligned}
	x'_1 {}:={} & \frac{
		(a'X+b'Y)
		{\left\\{\\; (x_1-e')X+(y_1-f')Y \\;\right\\}}
	}{{\left(a'X+b'Y\right)}^2+{\left(c'X+d'Y\right)}^2}
	\\\\
	y'_1 {}:={} & \frac{
		(c'X+d'Y)
		{\left\\{\\; (x_1-e')X+(y_1-f')Y \\;\right\\}}
	}{{\left(a'X+b'Y\right)}^2+{\left(c'X+d'Y\right)}^2}
	\\\\
	x'_2 {}:={} & \frac{
		(a'X+b'Y)
		{\left\\{\\; (x_2-e')X+(y_2-f')Y \\;\right\\}}
	}{{\left(a'X+b'Y\right)}^2+{\left(c'X+d'Y\right)}^2}
	\\\\
	y'_2 {}:={} & \frac{
		(c'X+d'Y)
		{\left\\{\\; (x_2-e')X+(y_2-f')Y \\;\right\\}}
	}{{\left(a'X+b'Y\right)}^2+{\left(c'X+d'Y\right)}^2}
\end{aligned} $$

開始点と終了点が同じような形の式で表せていることがわかる。

実際に上記の式を使って計算して、 `gradientTransform` を取り込む操作を行うと、操作を行う前後でグラデーションが変化していないことが確認できるため、これが取り込んだ後の座標を示す正しい式だと言えそうだ。

ちなみに3つの式 $ x'_2 - x'_1 = \alpha $, $ y'_2 - y'_1 = \beta $, $ \alpha x'_1 + \beta y'_1 = \gamma $ から終了点の乗る線を表した次の式が得られる。
$$
\alpha x'_2 + \beta y'_2 = \underbrace{
	\alpha^2 + \beta^2 + \gamma
}\_{\displaystyle =: \delta}
$$
先ほどは $ \alpha x'_1 + \beta y'_1 = \gamma $ の線から開始点を選んだが、代わりに $ \alpha x'_2 + \beta y'_2 = \delta $ の線上で原点から最も近い点を終了点に選ぶという方法も考えられる。
計算してみると分かるのだが、実は $ \alpha x'_1 + \beta y'_1 = \gamma $ から開始点を選ぶ方法でも $ \alpha x'_2 + \beta y'_2 = \delta $ から終了点を選ぶ方法でも同じ座標が得られる。 ($\gamma$ と $\delta$ の表式も似た形になっている)

### オマケ: 開始点と終了点の規格化

以上までで `gradientTransform` を取り込む際の開始点と終了点の変化の計算式を求めることができたが、この結果は取り込みたい場合以外にも使うことができる。

何度も言うように、同じグラデーションでも開始点と終了点の選び方は無数に存在するが、開始点と終了点の選び方を統一したい、というケースもあるだろう。その選び方を統一するのに先の計算結果が使えるのだ。ここでは、選び方を統一することを「規格化」と呼ぶことし、規格化の計算式を求める。

規格化の手法としてはやはり「原点から最も近い点を選ぶ」のが妥当である。とある既存の `linearGradient` において開始点 $P_1$ と終了点 $P_2$ の座標がそれぞれ $(x_1,y_1)$, $(x_2,y_2)$ になっているものとする。
これを正規化して得られる開始点と終了点の座標 $P'_1(x'_1,y'_1)$, $P'_2(x'_2,y'_2)$ は次のように得られる。

$$ \begin{aligned}
	x'_1 {}={} & \frac{X}{X^2+Y^2}(x_1X+y_1Y) \\\\
	y'_1 {}={} & \frac{Y}{X^2+Y^2}(x_1X+y_1Y) \\\\
	x'_2 {}={} & \frac{X}{X^2+Y^2}(x_2X+y_2Y) \\\\
	y'_2 {}={} & \frac{Y}{X^2+Y^2}(x_2X+y_2Y)
\end{aligned} $$

これは先ほど得た `gradientTransform` を取り込む際の計算結果において、変換関数を $\mathrm{matrix}(1,0,0,1,0,0)$ (つまり単位行列) にした結果に相当する。

## `radialGradient` における変形関数の取り込み

次に `radialGradient` 要素において `gradientTransform` 属性を取り込む計算を考えてみる。

### 原理

まず、 `linearGradient` の場合と同様に座標 $(p,y)$ とグラデーションの進み具合 $p$ の間の関係式を求める。

`radialGradient` のパラメータは開始円の中心座標と半径、終了円の中心座標と半径から構成される。開始円の方を $r_1$, $(x_1,y_1)$ 、終了円の方を $r_2$, $(x_2,y_2)$ と表すことにする。
また、グラデーションの方向を定めるベクトルは開始円上の点から終了円上の点に向かって定められるが、2つの点はそれぞれの円の中心から見て同じ方向になっていることから、その方向の角度を表すパラメータとして $\theta$ も用意する。

すると、次のように表されるはずだ。

$$ \begin{aligned}
	\begin{bmatrix}\\; x \\;\\\\\\; y \\;\end{bmatrix}
	{}={} &
	(1-p)
	\begin{bmatrix}\\; r_1 \cos\theta + x_1 \\;\\\\\\; r_1 \sin\theta + y_1 \\;\end{bmatrix} +
	p
	\begin{bmatrix}\\; r_2 \cos\theta + x_2 \\;\\\\\\; r_2 \sin\theta + y_2 \\;\end{bmatrix}
	\\\\
	{}={} &
	\Bigl\\{\\; (1-p)\\;r_1 + p\\;r_2 \\;\Bigr\\}
	\begin{bmatrix}\\; \cos\theta \\;\\\\\\; \sin\theta \\;\end{bmatrix} +
	\left\\{\\;
		(1-p)
		\begin{bmatrix}\\; x_1 \\;\\\\\\; y_1 \\;\end{bmatrix} +
		p
		\begin{bmatrix}\\; x_2 \\;\\\\\\; y_2 \\;\end{bmatrix}
	\\;\right\\}
\end{aligned} $$

逆に言えば、座標 $(x,y)$ における色はこの式を解いて得られる $p$ によって決定される。

`gradientTransform` で $\mathrm{matrix}(a,b,c,d,e,f)$ が作用した場合、作用後の点 $(x',y')$ における $p$ と $\theta$ は次のように得られる。

$$ \begin{aligned}
	\begin{bmatrix}\\; x' \\;\\\\\\; y' \\;\end{bmatrix}
	{}={} &
	\begin{bmatrix}\\; a & c \\;\\\\\\; b & d \\;\end{bmatrix}
	\begin{bmatrix}\\; x \\;\\\\\\; y \\;\end{bmatrix} +
	\begin{bmatrix}\\; e \\;\\\\\\; f \\;\end{bmatrix}
	\\\\ {}={} &
	\underbrace{
		\Bigl\\{\\; (1-p)\\;r_1 + p\\;r_2 \\;\Bigr\\}
		\begin{bmatrix}\\; a & c \\;\\\\\\; b & d \\;\end{bmatrix}
		\begin{bmatrix}\\; \cos\theta \\;\\\\\\; \sin\theta \\;\end{bmatrix}
	}\_{\displaystyle =: R}
	\\\\ & \quad +
	\underbrace{
		\begin{bmatrix}\\; a & c \\;\\\\\\; b & d \\;\end{bmatrix} \left\\{\\;
			(1-p)
			\begin{bmatrix}\\; x_1 \\;\\\\\\; y_1 \\;\end{bmatrix} +
			p
			\begin{bmatrix}\\; x_2 \\;\\\\\\; y_2 \\;\end{bmatrix}
		\\;\right\\} +
		\begin{bmatrix}\\; e \\;\\\\\\; f \\;\end{bmatrix}
	}\_{\displaystyle =: C}
	\\\\ {}={} & R + C
\end{aligned} $$

`radialGradient` における `gradientTransform` の取り込みとは、上式が以下の式に当てはまるように開始円 $r'_1$, $x'_1$, $y'_1$ と終了円 $r'_2$, $x'_2$, $y'_2$ 及び角度 $\theta$ を定めることをいう。

$$ \begin{alignedat}{3}
	\begin{bmatrix}\\; x' \\;\\\\\\; y' \\;\end{bmatrix}
	{}={} &
	\underbrace{
		\Bigl\\{\\; (1-p)\\;r'_1 + p\\;r'_2 \\;\Bigr\\}
		\begin{bmatrix}\\; \cos\theta' \\;\\\\\\; \sin\theta' \\;\end{bmatrix}
	}\_{\displaystyle =: R' }
	& {}+{} \biggl\\{\\; &
	\underbrace{
		(1-p)
		\begin{bmatrix}\\; x'_1 \\;\\\\\\; y'_1 \\;\end{bmatrix} +
		p
		\begin{bmatrix}\\; x'_2 \\;\\\\\\; y'_2 \\;\end{bmatrix}
	}\_{\displaystyle =: C' } & \\;\biggr\\}
	\\\\ {}={} & R' + C'
\end{alignedat} $$

$\sin\theta$, $\cos\theta$ の有無を踏まえると、 $ R = R' $ と $ C = C' $ を別個に計算するのが良さそう。

### 変換関数の条件

取り込む条件は上で定めたが、任意の $\mathrm{matrix}(a,b,c,d,e,f)$ でこれに当てはめて取り込める訳ではない。
計算の詳細は省略するが、 $ (a,b) = (d,-c) $ 或いは $ (a,b) = (-d,c) $ を満たす場合でないと取り込むことができない。
以下ではこのどちらかを満たす場合を取り扱うのだが、それぞれに対して、
$\mathrm{matrix}(a,b,c,d,e,f)$ を取り扱いに便利な表式で書き換える。

* $ (a,b) = (d,-c) $ の場合
$$ \begin{aligned}
a {}={} & k \cos\varphi & \quad
c {}={} & - k \sin\varphi \\\\
b {}={} & k \sin\varphi & \quad
d {}={} & \phantom{-} k \cos\varphi
\end{aligned} $$

* $ (a,b) = (-d,c) $ の場合
$$ \begin{aligned}
a {}={} & l \cos\omega & \quad
c {}={} & \phantom{-} l \sin\omega \\\\
b {}={} & l \sin\omega & \quad
d {}={} & - l \cos\omega
\end{aligned} $$

$k$, $\varphi$, $l$, $\omega$ を適切に選べば必ずこれを満たすことができる。

#### 補足: 取り込むことができる条件について

$ (a,b) = (d,-c) $ 或いは $ (a,b) = (-d,c) $ でないと満たせない図形的な意味は何となく説明できそうである。

`radialGradient` 要素は、そのパラメータを見ても分かる通り、正円状に広がるグラデーションのみを指定することができて、楕円は指定できない。
上に示した条件を満たさない変換というのは、等方的でない伸縮が発生する。等方的でないと正円は楕円に変化してしまう。楕円は `radialGradient` のパラメタだけで記述できないから、取り込み操作によって `gradientTransform` を消すことはできない。

### 取り込んだ開始円と終了円を求める

上記の条件を満たす場合に、 $R$ と $C$ を改めて計算すると

* $ (a,b) = (d,-c) $ の場合
$$ \begin{aligned}
	R {}={} &
	\Bigl\\{\\; (1-p)\\;r_1 + p\\;r_2 \\;\Bigr\\} \cdot k
	\begin{bmatrix}\\;
		\cos\varphi & - \sin\varphi
		\\;\\\\\\;
		\sin\varphi & \phantom{-} \cos\varphi
	\\;\end{bmatrix} \cdot
	\begin{bmatrix}\\; \cos\theta \\;\\\\\\; \sin\theta \\;\end{bmatrix}
	\\\\ {}={} &
	\Bigl\\{\\; (1-p)\\;(k r_1) + p\\;(k r_2) \\;\Bigr\\}
	\begin{bmatrix}\\; \cos(\varphi+\theta) \\;\\\\\\; \sin(\varphi+\theta) \\;\end{bmatrix}
	\\\\\\\\
	C {}={} &
	\begin{bmatrix}\\;
		\mathrm{c}\varphi & - \mathrm{s}\varphi
		\\;\\\\\\;
		\mathrm{s}\varphi & \phantom{-} \mathrm{c}\varphi
	\\;\end{bmatrix}
	\left\\{\\;
		(1-p)
		\begin{bmatrix}\\; x_1 \\;\\\\\\; y_1 \\;\end{bmatrix} +
		p
		\begin{bmatrix}\\; x_2 \\;\\\\\\; y_2 \\;\end{bmatrix}
	\\;\right\\} +
	\begin{bmatrix}\\; e \\;\\\\\\; f \\;\end{bmatrix}
	\\\\ {}={} &
	(1-p) \cdot \begin{bmatrix}\\;
		x_1 \\; \mathrm{c}\varphi - y_1 \\; \mathrm{s}\varphi + e
		\\;\\\\\\;
		x_1 \\; \mathrm{s}\varphi + y_1 \\; \mathrm{c}\varphi + f
	\\;\end{bmatrix}
	\\\\ & \phantom{(1)} +
	p \cdot \begin{bmatrix}\\;
		x_2 \\; \mathrm{c}\varphi - y_2 \\; \mathrm{s}\varphi + e
		\\;\\\\\\;
		x_2 \\; \mathrm{s}\varphi + y_2 \\; \mathrm{c}\varphi + f
	\\;\end{bmatrix}
\end{aligned} $$

* $ (a,b) = (-d,c) $ の場合
$$ \begin{aligned}
	R {}={} &
	\Bigl\\{\\; (1-p)\\;r_1 + p\\;r_2 \\;\Bigr\\} \cdot l
	\begin{bmatrix}\\;
		\cos\omega & \phantom{-} \sin\omega
		\\;\\\\\\;
		\sin\omega & - \cos\omega
	\\;\end{bmatrix} \cdot
	\begin{bmatrix}\\; \cos\theta \\;\\\\\\; \sin\theta \\;\end{bmatrix}
	\\\\ {}={} &
	\Bigl\\{\\; (1-p)\\;(l r_1) + p\\;(l r_2) \\;\Bigr\\}
	\begin{bmatrix}\\; \cos(\omega-\theta) \\;\\\\\\; \sin(\omega-\theta) \\;\end{bmatrix}
	\\\\\\\\
	C {}={} &
	\begin{bmatrix}\\;
		\mathrm{c}\omega & \phantom{-} \mathrm{s}\omega
		\\;\\\\\\;
		\mathrm{s}\omega & - \mathrm{c}\omega
	\\;\end{bmatrix}
	\left\\{\\;
		(1-p)
		\begin{bmatrix}\\; x_1 \\;\\\\\\; y_1 \\;\end{bmatrix} +
		p
		\begin{bmatrix}\\; x_2 \\;\\\\\\; y_2 \\;\end{bmatrix}
	\\;\right\\} +
	\begin{bmatrix}\\; e \\;\\\\\\; f \\;\end{bmatrix}
	\\\\ {}={} &
	(1-p) \cdot \begin{bmatrix}\\;
		x_1 \\; \mathrm{c}\omega + y_1 \\; \mathrm{s}\omega + e
		\\;\\\\\\;
		x_1 \\; \mathrm{s}\omega - y_1 \\; \mathrm{c}\omega + f
	\\;\end{bmatrix}
	\\\\ & \phantom{(1)} +
	p \cdot \begin{bmatrix}\\;
		x_2 \\; \mathrm{c}\omega + y_2 \\; \mathrm{s}\omega + e
		\\;\\\\\\;
		x_2 \\; \mathrm{s}\omega - y_2 \\; \mathrm{c}\omega + f
	\\;\end{bmatrix}
\end{aligned} $$

$ R = R' $ 及び $ C = C' $ から、開始円と終了円の半径と中心座標、そして方向は次のように取り込まれる。

* $ (a,b) = (d,-c) $ の場合
$$
r'_1 = k r_1 \qquad r'_2 = k r_2
\qquad \theta' = \varphi + \theta
$$ $$ \begin{alignedat}{4}
	x'_1 {}={} &
		x_1 & \cos\varphi & {}-{} &&
		y_1 & \sin\varphi & {}+{} e
	\\\\
	y'_1 {}={} &
		x_1 & \sin\varphi & {}+{} &&
		y_1 & \cos\varphi & {}+{} f
	\\\\
	x'_2 {}={} &
		x_2 & \cos\varphi & {}-{} &&
		y_2 & \sin\varphi & {}+{} e
	\\\\
	y'_2 {}={} &
		x_2 & \sin\varphi & {}+{} &&
		y_2 & \cos\varphi & {}+{} f
\end{alignedat} $$

* $ (a,b) = (-d,c) $ の場合
$$
r'_1 = l r_1 \qquad r'_2 = l r_2
\qquad \theta' = \omega - \theta
$$ $$ \begin{alignedat}{4}
	x'_1 {}={} &
		x_1 & \cos\omega & {}+{} &&
		y_1 & \sin\omega & {}+{} e
	\\\\
	y'_1 {}={} &
		x_1 & \sin\omega & {}-{} &&
		y_1 & \cos\omega & {}+{} f
	\\\\
	x'_2 {}={} &
		x_2 & \cos\omega & {}+{} &&
		y_2 & \sin\omega & {}+{} e
	\\\\
	y'_2 {}={} &
		x_2 & \sin\omega & {}-{} &&
		y_2 & \cos\omega & {}+{} f
\end{alignedat} $$

## まとめ

`linearGradient` と `radialGradient` で変形を伴う `gradientTransform` 属性の値を取り込む方法を考えた。
`linearGradient` については原理を踏まえることで一意には定まらないが常に取り込むことが可能であった。
一方で、 `radialGradient` については特定の条件下で取り込むことが可能であった。

今回はグラデーション要素における `gradientTransform` 属性の変形を取り込む計算方法を求めたが、今後は `path`, `circle`, `rect` 要素などの図形要素に対する `transform` 属性の変形を取り込む計算についても調べていく予定である。