PerfectVector
著者: Irene Kim2 分で読める

KonvaのSVG読み込み:画像か編集可能なパスかを選ぶ

SVGをKonvaへ1枚の画像または別々の編集可能なパスとして読み込みます。色変更、拡大縮小、PNG書き出しを検証し、将来の編集用に元SVGを保持します。

目次

完全なSVGをcanvasへ配置したい場合はKonva.Imageを使います。アプリで個々のパス形状の色を変えたり操作したりする必要がある場合は、別々のKonva.Pathノードを使います。SVG画像を読み込んでも、その中のすべての要素にKonvaオブジェクトが自動で作られるわけではありません。KonvaのSVGガイドは、これらを異なる方法として説明しています。

ロゴ選択や構図作成のツールでは、1枚の画像で十分な場合があります。2色のバッジで各領域に別の操作が必要なら、オブジェクト構造が重要です。読み込み方法を選ぶ前に、ユーザーが何を編集する必要があるか決め、canvasのシーンとともにSVGの元データを保持します。

読み込み前に表現方法を選ぶ

アプリに必要な操作最初に使うもの管理する情報
SVG全体の移動やリサイズKonva.Image元画像と表示寸法
既知のパス領域の色を独立して変える領域ごとのKonva.Pathパスデータ、塗り、位置、グループ化
複雑なSVGの見た目を保持対象ブラウザーで検証した画像描画元SVGと外部リソース
任意のSVG文書を編集意図して設計したSVG解析・編集の手順変換、スタイル、文字、効果、書き出しの再現性

KonvaのPathチュートリアルは、dataからSVGパスデータを受け取ります。これはパスのd属性の値であり、SVG文書全体ではありません。d値だけのコピーでは、親グループの変換、継承した色、SVGのviewBoxの動作は引き継がれません。

元ファイルに、そもそもパスがあるかという別の問題もあります。SVGで包んだビットマップなら、埋め込みラスターの検査ガイドを使います。正しいパスベースのSVGでも、Konvaでは1枚の画像ノードになり得ます。そのことが、元ファイルが偽のベクターであることを意味するわけではありません。

塗りの違いが分かる2色の例

以下の独自マークには、塗りを明示した手書きの2本のパスがあります。左のcanvasは両方を1枚の画像として読み込みます。右は同じ形状から2つのPathノードを作ります。両方にピンクの塗りの変更を要求し、倍率1.3を設定します。

Konvaの比較:色の変わらない緑と黄色のSVG画像の背後にピンクが現れ、別々のパスではピンクと黄色に編集されています
同じ独自形状をKonva 10.6.0で描画しました。ImageのfillはSVGの背後の矩形に色を付け、最初のPathのfillはその領域の色を変えます。両方とも倍率1.3です。

左のピンクの矩形は、挙動を示す有用な根拠です。imageNode.fill()は画像形状の背景を変えますが、不透明な緑と黄色の領域は元の色を保持します。右ではpaths[0].fill()が最初の領域だけを変えます。このテストは独自形状を使い、PerfectVectorの変換結果ではありません。

以下をHTMLファイルとして保存し、ローカル開発サーバーから配信します。Konva 10.6.0を固定し、320 × 240のステージを2つ作って、描画前にSVG画像のデコードを待ちます。画像の撮影で実行したものと同じ比較コードです。

<div id="image-scene"></div>
<div id="path-scene"></div>
<script src="https://unpkg.com/konva@10.6.0/konva.min.js"></script>
<script type="module">
const parts = [
  { data: 'M20 80 C20 30 70 20 100 40 L100 140 C60 140 20 120 20 80 Z', fill: '#176b55' },
  { data: 'M100 40 C150 20 180 50 180 85 C180 120 145 140 100 140 Z', fill: '#e9b949' }
];
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="200" height="160" viewBox="0 0 200 160">${parts.map(p => `<path d="${p.data}" fill="${p.fill}"/>`).join('')}</svg>`;
const source = new Image();
source.src = 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(svg);
await source.decode();
 
function makeStage(container) {
  const stage = new Konva.Stage({ container, width: 320, height: 240 });
  const layer = new Konva.Layer();
  stage.add(layer);
  return { stage, layer };
}
const imageScene = makeStage('image-scene');
const pathScene = makeStage('path-scene');
const imageNode = new Konva.Image({
  image: source, x: 30, y: 16, width: 200, height: 160
});
imageScene.layer.add(imageNode);
const group = new Konva.Group({ x: 30, y: 16 });
const paths = parts.map(p => new Konva.Path(p));
group.add(...paths);
pathScene.layer.add(group);
 
// Compare the same fill request on the two representations.
imageNode.fill('#db567d');
paths[0].fill('#db567d');
 
// Resize both without changing their source geometry.
imageNode.scale({ x: 1.3, y: 1.3 });
group.scale({ x: 1.3, y: 1.3 });
imageScene.layer.draw();
pathScene.layer.draw();
 
const png = pathScene.stage.toDataURL({ pixelRatio: 2 });
const exported = new Image();
exported.src = png;
await exported.decode();
</script>

意図して小さくしたこの例では、各パスが独自の描画設定を持ち、同じ座標を使います。グループが2つの編集可能なパスに共通の位置と倍率を与えます。グループの倍率を変えても、各パスのdataはそのままです。描画する場所とサイズが変わります。

信頼できる既存SVGを画像全体として配置するだけなら、Imageチュートリアルで通常の読み込み手順を確認できます。SVGのURLを画像として読み込み、読み込み完了を待ってからKonva.Imageへ渡します。SVGマークアップを描画するReactコンポーネントは、canvasが求める画像オブジェクトとは異なる値です。

複雑な画像ではパスデータ以外も引き継ぐ

例のparts配列は、既知の2つの形状の明示的なモデルです。SVGのインポーターではありません。書き出した画像に同様の方法を使う前に、何が見た目を作るか確認します。

  • グループ変換はすべての子を移動、回転、拡大縮小します。その関係を保持するか、意図して変換を形状へ適用します。
  • 描画設定は祖先やCSSから来る場合があります。各パスが宣言していると想定せず、適用される塗りとストロークを特定します。
  • グラデーション、クリッピング、マスク、フィルターには独自の定義と参照があります。コピーしたd文字列は、それらの効果を復元しません。
  • 文字や基本図形は別の処理が必要な場合があります。見えるすべてのオブジェクトがすでにパスだと想定しないでください。

テスト中は、再構築した画像の横に元の描画を置きます。分かりやすい1つの領域から始め、位置と色を比較してから残りの形状を加えます。構造が扱いにくい場合は、SVGの編集ガイドで元データの整理を確認できます。

個々のパス操作が不要なら、完全なSVGを画像として読み込むことで復元作業を避けられます。Konvaは、canvgなどの外部ライブラリでSVGを描画し、そのcanvasをKonva.Imageへ配置する方法も説明しています。それも画像としての表現であり、元の各パスに別のKonvaノードを与えるものではありません。文書化された選択肢を参照してください。

元の色を変えるだけの画像では、SVG自体を更新して画像を再読み込みする方法もあります。Konvaの保守担当者はreact-konvaのissue 530でこの方法を説明しています。すべての塗りを無差別に置き換えて意図した色の違いを失うのではなく、既知のSVGに適した構造的な編集を使います。

Canvasの書き出しはラスター納品として扱う

例はpathScene.stage.toDataURL({ pixelRatio: 2 })を呼びます。320 × 240のステージから、デコード後に640 × 480のPNGが生成されます。pixelRatioを増やすと出力ピクセル寸法が変わりますが、PNGが編集可能なSVGになるわけではありません。Konvaの書き出しドキュメントが、この設定を説明しています。

次の3つの成果物を分けて保持します。

  1. ベクター編集と比較用の元SVG。
  2. パスや画像の参照、アプリが対応する編集を含むアプリのシーンデータ。
  3. 描画した画像が必要な用途向けのPNGなどのラスター出力。

書き出したPNGを開き直し、寸法と細部を確認します。アプリが対応するブラウザーでもSVG画像を試します。KonvaのSVGガイドは描画互換性の違いを指摘しています。この例は単純で自己完結したSVGを1つテストしたもので、すべての書き出しファイルや効果への対応を証明しません。

canvasのプレビューだけでベクター品質を判断しないでください。SVGのピクセルが目立つ問題のガイドは、元形状、ラスタープレビュー、表示サイズを区別しています。

元データにPerfectVectorが役立つ場合

開始素材がPNGやJPGのモチーフしかない場合は、Konvaのシーンを作る前に、PerfectVectorで編集可能なSVG形状を復元できます。画像をベクターへ変換し、エディターで結果を開き、輪郭、穴、色領域を確認してから、アプリへパスを対応させます。

ベクター化はKonvaのシーンモデルや任意のSVGパーサーを作りません。表現方法を選び、必要なプロパティを引き継ぐのは別の作業です。使えるSVGマスターがあれば、そのファイルから作業します。ピクセルを保持したい写真や塗りのテクスチャには、トレースせず画像ノードを使うのが適切な場合が多くあります。

自分の画像で読み込みを確認する

再利用できるエディターにSVGを加える前に、次を行います。

  1. 必要なユーザー操作に応じて、1つの全体画像ノードか、別々の形状ノードを選びます。
  2. 1つの領域の色を変え、意図したオブジェクトだけが変わるか確認します。
  3. デザイン全体をリサイズし、間隔、ストロークの見た目、クリッピングを確認します。
  4. 必要なピクセル寸法で書き出し、ラスターを開き直します。
  5. 保存したSVGマスターを独立して開き直し、canvasの書き出しだけが唯一のコピーにならないようにします。

よくある質問

Konva.ImageはSVG内のパスを編集可能にしますか? いいえ。SVGを1つの画像ノードとして表示します。個々のパス領域を操作するには、適したパスデータから別々のPathノードを作るか、文書構造を保持するSVGの編集手順を使います。

fillでSVGの色ではなく矩形が変わるのはなぜですか? Imageノードでは、fillは画像内容の背後の画像形状を塗ります。SVG内部の塗りは書き換えません。元SVGを編集して再読み込みするか、アプリに個別の色操作が必要なら別々のPathノードを使います。

pixelRatioを増やすとベクターファイルを書き出せますか? いいえ。この例ではpixelRatio 2で、ステージの幅と高さが2倍のPNGができます。ベクター編集用にはSVGの元データを別に保持します。

参考資料

  1. Konva — Canvas上のSVG — Image、Path、外部レンダラーの選択肢とブラウザー互換性の制限。
  2. Konva — Pathチュートリアル — SVGパスデータと明示的な描画プロパティからのPath構築。
  3. Konva — Imageチュートリアル — 画像の読み込みとKonvaのシーンへの配置。
  4. Konva — 高品質な画像書き出し — ラスター書き出しとpixelRatioが出力寸法に与える影響。
  5. Konva — react-konvaのissue 530 — SVG画像の色変更に対する、保守担当者の元データ編集と再読み込みの方法。

ラスターのアイコンから始める場合は、画像をSVGパスへ変換し、復元した色領域を確認してから、エディターの残りを組み込む前にKonvaで1つの領域をテストしてください。

ブログのその他の記事

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