Paper.jsのSVGブーリアン処理:画像を切り抜き、結合する
SVGパスをPaper.jsへ読み込み、重なる形を結合し、実際の穴を差し引きます。再現できる小さなブラウザー例で書き出し結果を確認します。
SVGを読み込み、目的のパスオブジェクトを見つけ、unite()で塗り領域を結合するか、subtract()で一方の領域から他方を取り除きます。結果を確認するまでは入力形状を残し、他の環境で使う前に書き出したSVGを開き直します。
重要な判断は、ブーリアン処理の前にあります。どの形をまとめるべきでしょうか。読み込んだグループには、背景矩形、クリッピングパス、文字、装飾部品が含まれる場合があります。すべての子を結合すると、保持したかった分離が失われる場合があります。この手順は名前のある3本の閉じたパスを使い、何が変わるか明確にします。
特定できる塗り形状から始める
Paper.jsのSVGインポーターは、SVGマークアップ、SVG要素、URLを受け取ります。それぞれ読み込みの扱いが異なります。この例はインライン文字列を読み込みます。リモートファイルは、読み込み後のインポートコールバックで処理します。
この作業の入力は、PathかCompoundPathオブジェクトでなければなりません。グループはコンテナーであり、ラスター画像はパスのブーリアン処理に必要な境界を提供しません。ファイルが1枚の画像のようにしか動作しないなら、まずSVGのグループを解除できない理由を確認します。
自分で管理する部品にはIDを使います。任意の書き出しでchildren[0]が「ロゴ」だと想定しないでください。未知の画像を調べ、部品を意図して割り当てます。既存の複合パスのサブパスが意図した穴を定義しているなら、そのまま保持します。
小さな読み込み、結合、差分処理を実行する
次のコードをPaper.js 0.12.18でブラウザー内でテストしました。画像は独自の手書きSVGで、四角い本体、重なるタブ、意図する穴を示す白い四角からなります。PerfectVectorの変換サンプルではありません。
空のフォルダーに、固定したライブラリをインストールします。
npm install --save-exact paper@0.12.18index.htmlとして保存します。
<!doctype html>
<meta charset="utf-8">
<title>SVG Boolean example</title>
<canvas id="work" width="300" height="220" hidden></canvas>
<div id="result" style="background:#d8e8e0; width:300px"></div>
<pre id="svg-output"></pre>
<script src="node_modules/paper/dist/paper-full.min.js"></script>
<script src="example.js"></script>次をexample.jsとして保存し、ブラウザーでHTMLを開きます。
paper.setup(document.getElementById('work'));
const source = `<svg xmlns="http://www.w3.org/2000/svg" width="300" height="220" viewBox="0 0 300 220">
<path id="body" fill="#176b62" d="M40 40H180V180H40Z"/>
<path id="tab" fill="#ee9852" d="M160 80H260V140H160Z"/>
<path id="hole" fill="#ffffff" d="M80 80H130V130H80Z"/>
</svg>`;
const imported = paper.project.importSVG(source, { insert: false, expandShapes: true });
function operand(name) {
const item = imported.getItem({ name });
if (!(item instanceof paper.Path || item instanceof paper.CompoundPath)) {
throw new Error(`Expected path geometry for ${name}`);
}
return item;
}
const joined = operand('body').unite(operand('tab'), { insert: false });
const cut = joined.subtract(operand('hole'), { insert: false });
cut.fillColor = '#176b62';
cut.strokeColor = null;
paper.project.activeLayer.addChild(cut);
const output = paper.project.exportSVG({ asString: true, bounds: new paper.Rectangle(0, 0, 300, 220) });
document.getElementById('result').innerHTML = output;
document.getElementById('svg-output').textContent = output;insert: falseにより、読み込み結果はシーン外に保持されます。expandShapes: trueは、対応する基本図形をパスへ変換するようインポーターに要求します。今回の素材はすでにpath要素です。読み込んだIDがコンテナーや別の型を指す場合があるので、明示的なクラス確認も重要です。
ブーリアンAPIは、新しいパスアイテムを返します。ここでは、最終的な切り抜き形状を追加するまで、結果をシーン外に保持します。元の部品はimported内に残るので、処理が失敗しても元データを作り直す必要はありません。
joined.subtract(hole)は、結合した輪郭から、穴にする領域を差し引きます。入力の順序を逆にすると、別の処理となり、空や予期しない結果になる場合があります。Paper.jsの既定の差分モードは、入力を面積として扱います。traceオプションはブーリアン形状に関するもので、PNGをベクターへトレースすることとは関係ありません。

形状とファイルとして結果を確認する
この例の結合はPath、差分は2つの輪郭を持つCompoundPathを返しました。最終境界はSVG単位でx = 40、y = 40、幅220、高さ140でした。画像は300 × 220の書き出しビューポート内にあり、境界と文書サイズを意図して区別しています。
穴の中心(105, 105)を確認しました。結合した輪郭の内側、差し引いた結果の外側でした。本体の塗りの点(60, 60)は内側に残りました。書き出したSVGを再読み込みすると境界が保持され、穴の中心は再び除外されました。この単純な素材での結果であり、すべての読み込みファイルの保証ではありません。
書き出しAPIはSVG文字列を返し、境界を明示できます。結果の下に表示されたマークアップをresult.svgへコピーし、独立して開きます。保存した出力には、2つの閉じたサブパスを持つ1つのpath要素があり、ブラウザーで直接開いても正しく描画されました。
自分の画像では、次を確認します。
- 元画像に対する輪郭と各開口部。
- 背景や隠れた補助形状を誤って含めていないか。
- 意図する塗り。1つの結合形状は、独立して編集できる2つの単色塗りを保持できません。
- 書き出したビューポート。特に画像が文書の端に達する場合。
- 実際に使うアプリでのファイル。
白い重ね図形は、白い紙では穴に見える場合があります。透明度を確認するため、対照的な背景へ結果を置きます。SVGの穴が埋まる問題のガイドは、関連する複合パスと塗り規則の問題を説明しています。
処理結果が誤った形になる場合
メソッドがない。 選んだアイテムのクラスを調べます。グループ内の目的のパスを探すか、読み込み時に対応する基本図形を変換します。すべての子孫を結合して解決しないでください。複合パスにも子があり、穴の関係が重要です。
結果が変わらない。 返されたアイテムを表示しているか確認します。元の形を表示したままだと、成功した差分を覆う場合があります。この例では最終結果だけをアクティブレイヤーに入れます。
ストロークが消える、意味が変わる。 この例は、閉じた塗り領域を処理します。見える太い線は、そのストロークが覆う領域と自動的に同じ形になるわけではありません。その輪郭を塗りの入力にする必要がある場合は、ベクターエディターでアウトライン化したコピーを準備し、読み込み前に確認します。編集可能な元データを保持してください。
細かい画像でうまく動かない。 イラスト全体へ連続処理する前に、2つの入力を取り出し、輪郭を確認します。重複、自己交差、小さな断片には個別の確認が必要です。この小さな例は、性能も広範なSVG互換性も測定していません。一度だけの修復なら、形状処理の仕組みを書くより、ベクターエディターの選択・ノードツールが簡単な場合があります。
元データがまだPNGやJPGの場合
編集可能な元データがない場合は、PerfectVectorの画像をベクターに変換する手順で、適したラスター画像からSVGを準備できます。結果をプレビューし、輪郭と抜きの部分を確認して、Paper.jsで試す候補をダウンロードします。
ベクター化は、プログラムがどの部品を結合・差し引くべきか特定しません。読み込み構造を確認し、正しい入力を選び、書き出し結果を確認する必要があります。元ベクターがあれば使い、単純な四角や円はトレースせず直接描きます。画像のベクター化の概要は、その元ファイルの判断を説明しています。
よくある質問
読み込んだSVGのグループでuniteを呼べますか? まずグループ内の目的のPathまたはCompoundPath入力を選びます。グループは構成を表すもので、すべての子を結合すると背景を含めたり意図した分離を失ったりする場合があります。
subtractは元のパスを削除しますか? 新しいパスアイテムを返します。この例では読み込みとブーリアン結果にinsert: falseを使い、最終結果だけを挿入します。元の入力は、読み込んだコンテナー内に残ります。
穴がまだ白く見えるのはなぜですか? 色のある背景で確認します。白い塗りオブジェクトは背後を覆い、差し引いた穴は背景を見せます。書き出したファイルを開き直し、その関係が残るか確認します。
ベクター化がブーリアンの整理も行いますか? いいえ。適したラスターからパス画像を復元できますが、アプリが結合する形状、除去する領域、結果の検証方法を選ぶ必要があります。
参考資料
- Paper.js — Projectリファレンス — SVGの読み込み、書き出し、境界、プロジェクト内容。
- Paper.js — Pathリファレンス — 結合と差分、返すパスアイテム、ブーリアンのオプション。
- Paper.js — CompoundPathリファレンス — 複合形状と構成するパス。
ラスター画像しかない場合は、SVGの候補を準備し、部品を確認してから、残りの画像を処理する前に、役立つ最小のブーリアン操作をテストします。

