
SVG path の M/L/C/Q/A — Bézier 曲線と楕円弧、W3C SVG 2 を読む
SVGのpathは、移動・直線・曲線・閉路をコマンドで指定します。M10,10 C20,20 40,20 50,10のような文字列も、現在点と制御点に分ければ読めます。本記事ではW3C SVG 2(2018年10月4日勧告候補)のPaths章と円弧の実装ノートに沿って、曲線の数学と編集時の注意点を整理します。
path コマンド一覧 — 10 種類 × 大文字/小文字
W3C SVG 2 仕様 §9.3 で定義されている path コマンドは以下の 10 種類です。各コマンドは大文字 (絶対座標) と小文字 (現在点からの相対座標) のペアで存在し、計20種の文字が使えます。ただしZ/zには座標引数がなく、両者は同じ動作です。
表が収まらない場合は、左右にスクロールできます。
| 文字 | 名前 | 引数 | 意味 |
|---|---|---|---|
| M / m | moveto | x y | ペンを移動 (描画なし) |
| L / l | lineto | x y | 直線 |
| H / h | horizontal lineto | x | 水平線 |
| V / v | vertical lineto | y | 垂直線 |
| C / c | cubic Bézier | x1 y1 x2 y2 x y | 3 次 Bézier 曲線 |
| S / s | smooth cubic | x2 y2 x y | 前のC/Sに応じて制御点を反転 |
| Q / q | quadratic Bézier | x1 y1 x y | 2 次 Bézier 曲線 |
| T / t | smooth quadratic | x y | 前のQ/Tに応じて制御点を反転 |
| A / a | elliptical arc | rx ry x-axis-rotation large-arc-flag sweep-flag x y | 楕円弧 |
| Z / z | closepath | (なし) | 現在のサブパスを閉じる |
同じコマンドの引数を連続させるとコマンド文字を省略できます (例: L 10,20 30,40 50,60 = 3本の連続線)。これが SVG ファイルが暗号のように見える主因です。
Mに続く2組目以降の座標はLとして扱われます。Zは単にLで戻る場合と異なり、ストロークの端を閉路の接合として扱います。
3次Bézier曲線と4つの制御点
Bézier曲線は、制御点を用いて形を指定するパラメトリック曲線です。SVGでは2次と3次の曲線を直接指定できます。ここでは3次の場合を見ます。
3 次 Bézier 曲線は 4 つの制御点 P₀, P₁, P₂, P₃ から定義され、パラメータ t ∈ [0, 1] による曲線:
B(t) = (1-t)³ P₀ + 3(1-t)² t P₁ + 3(1-t) t² P₂ + t³ P₃
この式はバーンスタイン基底関数の和の形 (binomial expansion of (1-t)+t = 1) で、以下の重要な性質を持ちます。
- 端点保存: t=0 で P₀、t=1 で P₃ を必ず通る (中間のP₁、P₂は一般には通らない)
- 接線方向: 導関数はB′(0)=3(P₁−P₀)、B′(1)=3(P₃−P₂)。ベクトルが0なら接線方向はこの式だけでは決まらない
- 凸包性: 曲線は 4 制御点の凸包の内側に必ず収まる (描画範囲が予測しやすい)
- アフィン不変性: 制御点を平行移動・回転・スケールすると曲線も同様に変換される
SVG の C コマンド C x1,y1 x2,y2 x,y は、現在の点を P₀、引数の (x1,y1) を P₁、(x2,y2) を P₂、(x,y) を P₃ として扱います。
de Casteljau アルゴリズム — 数値的に安定な評価
Bézier 曲線を数値計算する際、上記の多項式を直接展開するよりde Casteljau アルゴリズム を使う方が安定です。これは線形補間を再帰的に適用する手法で、浮動小数点の丸め誤差がなくなるわけではありません。
// 3 次 Bézier の de Casteljau (パラメータ t での点を求める)
function deCasteljau(P0, P1, P2, P3, t):
Q0 = lerp(P0, P1, t) // (1-t)P0 + t P1
Q1 = lerp(P1, P2, t)
Q2 = lerp(P2, P3, t)
R0 = lerp(Q0, Q1, t)
R1 = lerp(Q1, Q2, t)
return lerp(R0, R1, t)
このアルゴリズムは曲線分割にも使えます。t = 0.5 で得られる点と中間結果を組み合わせれば、元の曲線を 元の形状を正確に分けた2本のBézier に分割でき、SVG エディタの「曲線の途中に点を追加」機能の数学的基礎になっています。各エディタの全編集操作が同じ実装とは限りません。
S / T コマンド — 滑らかな接続
Sは直前がCまたはSの場合に、その最後の制御点を現在点に関して反転し、新しい第1制御点にします。直前がそれ以外なら第1制御点は現在点です。Tも直前がQまたはTの場合だけ反転し、それ以外では現在点を制御点にします。
reflectedX = 2 * currentX - previousControlX
reflectedY = 2 * currentY - previousControlY
M 0,0 C 10,10 30,10 40,20 S 60,10 70,20
M 0,0 C 10,10 30,10 40,20 C 50,30 60,10 70,20この2つのpathは同じ曲線です。同じ次数で各区間をt∈[0,1]として比べると、反転により接続点の1階導関数が一致します。直前コマンドの条件を無視して、S/Tがあらゆる接続を滑らかにするとは扱えません。
A コマンド — 楕円弧の 7 引数
楕円弧 A は SVG path で最も難解とされるコマンドです。引数は 7 つ:
A rx ry x-axis-rotation large-arc-flag sweep-flag x y
- rx, ry: 楕円の半径 (x 方向、y 方向)
- x-axis-rotation: 楕円自体の回転角度 (度)
- large-arc-flag: 0は180度以下、1は180度以上の弧を選択。半円では区別できない場合がある
- sweep-flag: 0は負、1は正の角度方向。通常のy軸下向きのSVG座標系では、それぞれ反時計回り・時計回りに見える
- x, y: 終点の座標
一般には、指定された半径と回転で端点を通る2つの楕円と、それぞれの弧から最大4通りが候補になります。ただし半径補正後の半円など、フラグの組合せが同じ形に重なる場合があります。負の半径は絶対値として扱い、どちらかの半径が0なら直線、始点と終点が同じなら円弧を省略します。1個のAで全円を指定することはできません。
endpoint → 中心点変換 — W3C SVG 1.1 Implementation Notes F.6.5
SVG の A コマンドは「始点 + 終点 + 半径 + フラグ」で曲線を指定しますが、中心形式を使う処理では「中心 + 半径 + 開始角 + 角度範囲」が必要です。両者を結ぶ変換式は W3C SVG 1.1 Implementation Notes Appendix F.6.5 で定義されています。
// 入力: 始点 (x1,y1), 終点 (x2,y2), rx, ry, φ (回転), fA (large), fS (sweep)
// 先に同一点・半径0を処理し、rx, ryは絶対値にする
// 三角関数に渡すφは度からラジアンへ変換する
// Step 1: 楕円が回転していない座標系に変換
x1' = cos(φ)·(x1-x2)/2 + sin(φ)·(y1-y2)/2
y1' = -sin(φ)·(x1-x2)/2 + cos(φ)·(y1-y2)/2
// Step 2: 半径補正 (端点間距離が直径より長いとき自動拡大)
λ = (x1'/rx)² + (y1'/ry)²
if λ > 1:
rx *= sqrt(λ)
ry *= sqrt(λ)
// Step 3: 中心点 (回転前座標で)
sign = (fA != fS) ? +1 : -1
factor = sign · sqrt(max(0, (rx²·ry² - rx²·y1'² - ry²·x1'²) / (rx²·y1'² + ry²·x1'²)))
cx' = factor · rx·y1' / ry
cy' = factor · -ry·x1' / rx
// Step 4: 元の座標系に戻す
cx = cos(φ)·cx' - sin(φ)·cy' + (x1+x2)/2
cy = sin(φ)·cx' + cos(φ)·cy' + (y1+y2)/2
// Step 5: 開始角と掃引角度
θ_start = atan2((y1'-cy')/ry, (x1'-cx')/rx)
Δθ = atan2((-y1'-cy')/ry, (-x1'-cx')/rx) - θ_start
if !fS and Δθ > 0: Δθ -= 2π
if fS and Δθ < 0: Δθ += 2π
疑似コードでは半径補正後の丸め誤差による小さな負値を0に丸めています。極端な値ではオーバーフロー等にも配慮が必要で、完成済みの数値ライブラリではありません。特定エディタの内部実装を保証するものでもありません。
実例 — 円を path で描く 4 つの方法
半径 50 の円を中心 (100, 100) に描く path を 4 通りに書き分けてみます。
// 方法 1: circle 要素 (path ではない)
<circle cx="100" cy="100" r="50"/>
// 方法 2: A コマンド 2 個 (上半分 + 下半分)
<path d="M 50,100 A 50,50 0 0 1 150,100 A 50,50 0 0 1 50,100"/>
// 方法 3: C コマンド 4 個 (近似。丸め前のκでは最大半径方向誤差は半径の約0.0273%)
// κ = 4·(√2 - 1) / 3 ≒ 0.5523
<path d="M 100,50 C 127.614237,50 150,72.385763 150,100 C 150,127.614237 127.614237,150 100,150 C 72.385763,150 50,127.614237 50,100 C 50,72.385763 72.385763,50 100,50"/>
// 方法 4: Q コマンド 4 個 (近似、誤差大、円というより四角に近い)
<path d="M 100,50 Q 150,50 150,100 Q 150,150 100,150 Q 50,150 50,100 Q 50,50 100,50"/>
κ = 4(√2−1)/3は端点と中央を円に合わせる古典的な近似です。あらゆる誤差基準で最適な係数ではなく、最大半径方向誤差をさらに小さくする別の近似もあります。CやQによる多項式曲線は、一般に円弧そのものではありません。
SVGOのconvertShapeToPathはconvertArcs設定でcircle/ellipseをpath化できますが、「常に4つのCへ変換する」機能ではありません。要素数や表現を変えることが、必ずサイズ削減につながるわけでもありません。
Bézier の連続性 (G⁰ / G¹ / C¹ / C²)
曲線の接続は、位置・接線方向・導関数を分けて考えます。ここでは各区間のパラメータをt∈[0,1]とします。
表が収まらない場合は、左右にスクロールできます。
| 条件 | 意味 |
|---|---|
| G⁰ | 位置が一致。角があるかどうかは未指定 |
| G¹ | 位置と接線方向が一致(正則な接続を想定) |
| C¹ | 位置と1階導関数が一致 |
| C² | C¹に加え2階導関数が一致 |
曲率が連続という幾何学的条件とC²は同一ではありません。S/Tの反転が保証するのは、前述の条件での1階導関数の一致までです。SVG pathにはBスプラインやNURBSを直接指定するコマンドはなく、必要なら対応する区間や近似へ変換します。
SVG エディタが path を最適化するロジック
pathのサイズ削減では、次のような方法を検討します。
- 連続点を集約:
L 10,10 L 20,20→L 10,10 20,20(コマンド文字省略) - 絶対 → 相対座標 (条件付き): 大きな値より小さな相対値の方が文字数が少ないとき
- 小数桁削減:
50.000001→50 - shape → path:
<rect>をM ... L ... L ... L ... Zに変換 - 連続曲線を S/T に: 制御点が対称なら S/T で省略
- 結合可能な path をマージ: 同属性の連続パスを1つに
コマンドの省略と座標の丸めは別です。丸めは形状を変え得て、path結合は塗り・マーカー・アニメーション・外部参照にも影響する場合があります。削減率や完全な見た目の維持を保証せず、元画像を残し、実際の拡大率と利用箇所で比較します。
まとめ
- pathは10種類のコマンドで構成され、Z/zやMの繰返しには個別の規則がある。
- Bézierは制御点から評価でき、de Casteljauは評価と分割に使える。
- S/Tの反転には直前コマンドの条件がある。
- Aでは退化ケース、半径補正、フラグと座標系を確認する。
- 円のBézier近似や座標の丸めは、厳密な形状保存とは別である。
参考文献・ソース
記事作成に関する注記
本記事は AI(大規模言語モデル)を編集補助として活用して作成しています。 公開前に編集者が内容を確認していますが、事実誤認・仕様の解釈ミス・最新情報との齟齬が含まれる可能性があります。 重要な判断を行う際は、本文中の一次ソースや公式ドキュメントを必ずご自身でご確認ください。 誤りにお気づきの場合は、お問い合わせフォームよりご連絡いただけると助かります。




