PerfectVector
著者: Irene Kim

縮尺を混同せずにSVGパスの長さを測る

ブラウザーのユーザー単位でSVGパスを測り、変換と公称サイズを考慮します。輪郭の合計、pathLengthによる校正、機械の移動距離を区別しましょう。

2 分で読める
目次

SVGパスを測るには、形状要素のgetTotalLength()を呼び出し、その数値がどの座標系に基づくかを確認します。ブラウザーはユーザー単位の長さを返します。その数値が、そのまま表示上の長さ、ミリメートルの測定値、機械の移動距離になるわけではありません。

たとえば、今回の独自の斜線は50ユーザー単位です。均等に拡大するとルート空間での長さは100に倍増しますが、横方向だけを拡大すると約72.111になります。ローカルのAPI結果は、どちらも50のままです。これらは異なる測定の問いに答える数値です。

絵柄を測る前に既知の線分を測る

アクセス可能な形状を持つSVGと、インラインSVGとJavaScriptを実行するブラウザーが必要です。アートワークのコピーで作業してください。結果を記録する前に、必要なのがローカル形状、変換後の形状、指定した実寸での長さのどれかを決めます。

パスは、移動、直線、曲線、閉じるといったコマンドで経路を保存します。ユーザー単位は、そのSVGの座標系に属します。対応を確認せず、ピクセルやミリメートルと呼び替えないでください。MDNのgetTotalLength()のリファレンスは、ブラウザーが計算したユーザー単位のパス長を結果と定義しています。

次の完全なHTML例は直線を測ります。HTMLファイルとして保存し、ブラウザーで開いてください。

<!doctype html>
<html lang="en">
<meta charset="utf-8">
<title>SVG length control</title>
<svg width="240" height="240" viewBox="0 0 120 120">
  <path id="route" d="M10 10 L40 50"
        fill="none" stroke="black" />
</svg>
<pre id="result"></pre>
<script>
  const route = document.querySelector("#route");
  document.querySelector("#result").textContent =
    route.getTotalLength() + " local user units";
</script>
</html>

横の変化は30、縦の変化は40なので、想定する長さはsqrt(30² + 40²) = 50です。表示結果を既知の対照として確認してから、複雑な曲線や読み込んだエンブレムの測定を信頼します。240ピクセルのビューポートでも、このローカル結果が100ユーザー単位になるわけではありません。

今回のブラウザーテストでは、許容誤差を0.01ユーザー単位と定めました。半径10の4分の1円の対照は、解析的な長さが5π、約15.708です。テストブラウザーはChrome 155と報告し、結果は約15.7102で、許容誤差内でした。ブラウザーは曲線長を数値的に計算するため、有用な精度を残し、適切な許容誤差と比較してください。すべての実装で最後の小数桁まで一致することを求めないようにします。

ローカルの長さと変換後の長さを分ける

次の2つの例では、同じd="M10 10 L40 50"を維持して変換を加えます。

元の線分ローカルAPIの長さ指定した変換後の長さ(ルートSVGのユーザー単位)
変換なし5050
scale(2)50100
scale(2 1)50約72.111

均等な拡大縮小では、すべての方向に同じ大きさの倍率を掛けるため、2行目ではローカル長を2倍にできます。不均等な拡大縮小は方向ごとに異なります。3行目の変化後の線分は横60単位、下40単位なので、sqrt(60² + 40²)であり、50 × 2ではありません。

SVGの座標系仕様では、変換とviewBoxの対応が座標系を作る仕組みを説明しています。対象の空間を明記しましょう。ルートSVGのユーザー単位の長さも、ビューポートのCSSピクセルとは異なります。

この既知の直線では、端点をルートSVGのユーザー空間へ対応させました。

const root = route.ownerSVGElement;
const relative = root.getCTM().inverse().multiply(route.getCTM());
const a = new DOMPoint(10, 10).matrixTransform(relative);
const b = new DOMPoint(40, 50).matrixTransform(relative);
const transformedLength = Math.hypot(b.x - a.x, b.y - a.y);

ルートの逆行列はルートのビューポートへの対応を取り除き、相対行列はパスからルートのユーザー空間への変換を保持します。このコードは、ドキュメントに接続されたSVG、逆行列を持つ行列、上記の特定の直線を前提としています。端点は、そのパスのソースから分かっています。

曲線の場合、端点間の距離は弦の長さにすぎません。一般的に変換した曲線を測るには、形状を対象座標系に変換して測るか、別々の輪郭を保持する、管理された数値近似を使います。曲線の経路にこの端点の式を適用したり、不均等に変換された形状へ推測した1つの倍率を掛けたりしないでください。

均等・不均等な拡大縮小、2輪郭のエンブレム、ローカルパス長、公称ミリメートル計算を示す実際のブラウザー測定の対照
独自に作成した形状と、ブラウザーがその場で計算した結果です。ローカルの長さ、ルート空間での変換後の長さ、公称ミリメートルの計算を別々に示しています。受け取り側アプリや実物の制作テストは示していません。

対応関係を決めてからミリメートルへ変換する

独自のエンブレムのドキュメントは、width="50mm"、height="50mm"、viewBox="0 0 100 100"を宣言しています。意図した均等な対応では、1ユーザー単位は0.5 mmです。エンブレムの外側の幅は80単位なので、公称幅は40 mmです。

2つの輪郭の合計は380ユーザー単位です。宣言した縮尺では、次のようになります。

50 mm / 100 user units = 0.5 mm per user unit
380 user units × 0.5 mm per user unit = 190 mm of contours

これは作成時の寸法からの計算です。印刷物の測定、定規でのモニターの検証、エディターの読み込みサイズのテストはしていません。CSSはSVGのビューポートを上書きでき、受け取り先で絵柄が拡大縮小されることもあります。公称計算を制作の測定値と扱う前に、受け取り先で入稿寸法を確認してください。

ドキュメント全体の幅と、その中の絵柄の幅も異なります。代わりにエンブレムの外側の幅だけを50 mmにすると、80単位の幅から別の係数が得られます。50 / 80 = 0.625 mm per unitです。同じ380単位は、237.5 mmの輪郭に相当します。どの幅を設定したか記録してください。先に範囲を調べる必要があれば、SVGのバウンディングボックスのガイドを使います。

pathLengthは形状のサイズを変えずに距離を校正する

元の50単位の線分をコピーし、pathLength="1"も設定しました。getTotalLength()の結果は50のままでした。この属性は端点を変えず、形状の長さを1単位にするものでもありません。

SVG形状APIの仕様は、計算結果からpathLengthを明示的に除外しています。MDNのpathLengthリファレンスでは、破線を含む、パスに沿う距離の動作を校正することを説明しています。正規化した距離で効果を作る際に便利ですが、基となる経路の測定の代わりにはなりません。

線を描いて見せる表現には、SVGストロークアニメーションのガイドを参照してください。その校正された破線の値は、絵柄の確認用に記録する形状の長さと分けます。

輪郭を足しても機械の移動距離にはしない

今回のエンブレムには外周と菱形の穴があります。元の複合パスは、両方の閉じた輪郭を保存します。

<path fill-rule="evenodd"
      d="M10 10 H90 V60 L50 90 L10 60 Z
         M50 25 L65 45 L50 65 L35 45 Z" />

外側の輪郭は280単位、内側は100単位です。複合パスは380を返し、2つの輪郭を別々のパス要素として測って足しても380でした。SVGパスの仕様は、新しいサブパスと、直線・閉じる線分を区別しています。穴へ移動しても、輪郭同士を結ぶ描画線分は加わりません。

この合計が示すのは、選択した保存済み形状です。太い線をトレースすると2本の境界経路になる場合があり、重複パスは形状をもう一度加算し、エンブレムには加工するつもりのない小さな内側の輪郭が含まれる場合もあります。本当に対象とするオブジェクトと輪郭を測り、合計を解釈する前に確認してください。

機械の移動距離は別の結果です。加工には、輪郭間の移動、順序の選択、繰り返し加工、使用先固有のパス変更が含まれ得ます。ブラウザーでの合計から、それらは分かりません。切断時間、材料消費、機械での成功も示しません。スキャンした絵から1本の中心経路が必要なら、単線SVGのガイドで、その別の再構築作業を扱っています。

前提と一緒に測定値を記録する

  1. 選択した形状を特定し、複数の輪郭や重複が含まれるか確認します。
  2. 既知の対照と、選んだパスをローカルのユーザー単位で測ります。
  3. 変換と、結果に必要な座標空間を記録します。
  4. 単位を変換する前に、意図したドキュメントまたは絵柄の寸法を定めます。
  5. 校正値、輪郭の合計、受け取り先の測定値に別々のラベルを付けます。

既知の直線の対照が、宣言した許容誤差内で50を返さない場合は、選択した要素と元データの変更を確認します。ローカルの結果は一致するのに公称サイズが違うなら、パスを変える前にビューポート、変換、使用先の縮尺を調べてください。小数桁を増やしても、誤った座標の対応は解決しません。

別の人が選択した輪郭の合計を再現でき、その単位、対応、許容誤差、含まれるオブジェクトを理解できれば、ファイル形状の確認は完了です。保持しているブラウザーテストデータは、これらの数値の確認に合格しました。エディターへの読み込み、機械の移動、実寸のテストは行っていません。

元データがビットマップしかない場合、PerfectVectorの画像からベクターへのワークフローで、先に編集可能なSVG形状を再構築できます。復元した輪郭と穴を確認し、明示した縮尺で対象形状を測ってください。ベクター化はピクセルからパスを作るものであり、失われた実寸の復元、中心線の推定、機械移動の見積もりは行いません。既存のベクター原本があれば保持します。この記事のエンブレムは直接作成しており、トレースしていません。

よくある質問

getTotalLength()はピクセルとミリメートルのどちらを返しますか? ブラウザーが計算した、SVGユーザー単位の長さを返します。ビューポートのピクセルや公称の物理単位で表す前に、座標とサイズの対応を定めてください。

パスを拡大縮小してもローカルAPIの結果が変わらないのはなぜですか? 保存したローカル経路と変換後の表現は、異なる座標空間にあります。同じデータの線分は、均等・不均等な拡大縮小の両方で、ローカルでは50を返しました。変換後の長さは、ルートSVG単位で100と約72.111でした。

pathLength="1"ならパスは1単位の長さですか? 対応する、パスに沿う距離の動作へ校正値を与えます。形状APIが計算する長さは、この属性を無視します。今回の50単位の対照も50のままでした。

輪郭の合計から切断時間を見積もれますか? 合計だけでは不十分です。使用先が生成する経路、輪郭間の移動、繰り返し加工、速度変化、材料の特性は分かりません。実際の使用先の加工チェックと、適切な制作テストを使ってください。

出典

  1. MDN:SVGGeometryElement.getTotalLength() — ブラウザーが計算するユーザー単位のパス長です。
  2. W3C:SVG形状API — 計算される長さと、pathLengthの校正に依存しないことを説明しています。
  3. W3C:SVGの座標系 — ビューポート、viewBox、変換の対応を説明しています。
  4. MDN:pathLength — パスに沿う距離の動作の校正を説明しています。
  5. W3C:SVGパス — サブパス、描画線分、閉じるコマンドを説明しています。

測る前にビットマップから編集可能な形状が必要ですか?SVGの候補を作成し、含まれる輪郭を確認してから、意図した縮尺を定めて長さを記録しましょう。

ブログのその他の記事

PerfectVector

編集しやすい
よりきれいなSVGから始めましょう

クレジットカード不要