> [Site index](https://perfectvector.com/llms.txt) — available pages and descriptions.

# SVGからCanvasへ：PNG書き出し前に画像を読み込む

> Image.decodeでSVG全体を読み込み、選んだビットマップサイズで描画。CORSとPNGの読み取りを検証し、編集可能な元データと書き出しピクセルを分けます。

Source: https://perfectvector.com/ja/blog/svg-canvas-image-export

Language: ja
Author: Irene Kim
Published: 2026-10-06T08:00:00+09:00
Updated: 2026-10-06T08:00:00+09:00

SVG全体をcanvasに描くには、画像として読み込み、`image.decode()`を待ってから、その画像を`drawImage()`に渡します。その後、PNGの書き出しを別に確認してください。画像がcanvasに見えていても、ブラウザーがピクセルの読み取りや保存をブロックしている場合があります。

この経路はSVGドキュメントを画像として描きます。canvasに編集可能なSVGパスを渡すものではありません。選んだ輪郭を再構築したい場合は、[Path2DのSVGガイド](https://perfectvector.com/ja/blog/canvas-path2d-svg)を使ってください。どの表示経路を選んでも、元のSVGを保管します。

## デコード、描画、書き出しを分ける

`HTMLImageElement`は、[canvasのdrawImage()](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/drawImage)が受け取れる入力です。出力先の幅と高さが、描画サイズを制御します。[Image.decode()](https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/decode)は、画像のデコード完了時に解決するPromiseを返します。リクエストの失敗やデコードの問題で拒否される場合があります。

デコード成功は最初の確認にすぎません。CORSの許可なしで読み込んだクロスオリジン画像は、描けてもcanvasを汚染します。その後のピクセル読み取りや書き出しは失敗します。MDNの[CORS対応画像ガイド](https://developer.mozilla.org/en-US/docs/Web/HTML/How_to/CORS_enabled_image)が、この違いを説明しています。

**元データを残し、ピクセルを確認する**

![中空のひし形と円を、元の輪郭、滑らかな描画画像、ピクセル格子の出力として示す概念図](https://perfectvector.com/blog/svg-canvas-image-export/image-and-png.webp)

*模式図：元の輪郭と描画したピクセルは異なる表現です。枠、矢印、粗い格子は概念的なもので、以下のブラウザー結果は、独立して実行した自作のテストファイルに基づきます。*

## 自己完結した独自のSVGを作る

これを`master.svg`として保存します。一つのパスと一つの円を含み、埋め込み画像、フォント、外部依存はありません。元データでは、内側のひし形と周囲のキャンバスは透明です。

```svg
<svg xmlns="http://www.w3.org/2000/svg" width="240" height="160" viewBox="0 0 240 160">
  <path d="M96 24 L152 80 L96 136 L40 80 Z M96 58 L118 80 L96 102 L74 80 Z" fill="#176b5b" fill-rule="evenodd"/>
  <circle cx="200" cy="80" r="24" fill="#d4a53a"/>
</svg>
```

二つの図形と`viewBox`を含めた、ファイル全体を画像として使います。パスの`d`値だけを取り出す方法とは異なり、ブラウザーのSVG画像レンダラーにドキュメントを描画させます。

この例は意図的に自己完結させています。画像として使うSVGには、外部リソースやスクリプトなどの制約があります。[MDNの画像としてのSVGガイド](https://developer.mozilla.org/en-US/docs/Web/SVG/Guides/SVG_as_an_image)を参照してください。この小さなファイルのデコード成功を、任意のSVGが外部フォント、スタイル、リンクしたアートワークを読み込む証拠と考えないでください。提供方法の選択には、[インラインSVGと画像要素の比較](https://perfectvector.com/ja/blog/inline-svg-vs-img)が役立ちます。

## CORS応答が異なる二つのローカルオリジンを動かす

オリジンにはポートも含まれます。以下のローカル専用サーバーは、同じSVGバイトを二つのオリジンで提供します。二つ目のオリジンでは、`/cors/master.svg`は`Access-Control-Allow-Origin: *`を含み、`/plain/master.svg`は含みません。これで、アートワークと応答の許可を切り分けます。

これをSVGと以下のHTMLの隣に、`serve-proof.py`として保存します。`python3 serve-proof.py`を実行し、`http://127.0.0.1:8770/starter.html`を開きます。両方のポートが空いている必要があります。変更する場合は、サーバーとHTMLを一緒に更新してください。ターミナルでEnterを押すと、両サーバーが停止します。

```python
from http.server import ThreadingHTTPServer, SimpleHTTPRequestHandler
from pathlib import Path
from threading import Thread

root = Path(__file__).parent
class Handler(SimpleHTTPRequestHandler):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, directory=str(root), **kwargs)
    def do_GET(self):
        if self.path in ['/cors/master.svg', '/plain/master.svg']:
            payload = (root / 'master.svg').read_bytes()
            self.send_response(200)
            self.send_header('Content-Type', 'image/svg+xml')
            self.send_header('Cache-Control', 'no-store')
            if self.path.startswith('/cors/'):
                self.send_header('Access-Control-Allow-Origin', '*')
            self.end_headers()
            self.wfile.write(payload)
        else:
            super().do_GET()

servers = [ThreadingHTTPServer(('127.0.0.1', port), Handler) for port in (8770, 8783)]
for server in servers:
    Thread(target=server.serve_forever, daemon=True).start()
try:
    input('Original SVG proof on 127.0.0.1:8770 and :8783. Enter to stop.\n')
finally:
    for server in servers:
        server.shutdown()
        server.server_close()
```

この許可範囲の広いヘッダーは、自作のアートワークを提供するローカルテスト用です。本番の画像ホストでは、自分が管理するサーバーで、適切な許可オリジンを設定してください。画像属性を設定しても、別のサーバーの内容へのアクセスは許可されません。

## 描画の前に画像をデコードする

この完全なページを`starter.html`として保存します。各ケースに別のcanvasを用意するので、汚染されたケースが成功ケースを汚染しません。CORSが必要なリクエストでは、[MDNのcrossOriginリファレンス](https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/crossOrigin)の説明どおり、`src`より前に`crossOrigin`を設定します。

canvasのビットマップを`480 × 320`、CSS表示サイズを`240 × 160`に明示しています。元の画像の固有寸法は`240 × 160`で、`drawImage()`が選んだビットマップ寸法へ拡大縮小します。これは選択した寸法で、デバイスピクセル比率の計測ではありません。

```html
<!doctype html>
<html lang="en">
<meta charset="utf-8">
<title>Original SVG to canvas export</title>
<style>
  body { margin: 24px; font: 16px system-ui; background: #f6f2e8; }
  main { display: grid; grid-template-columns: repeat(2, 280px); gap: 20px; }
  figure { margin: 0; }
  canvas { display: block; width: 240px; height: 160px; background: white; }
  figcaption { margin: 6px 0; }
  pre { white-space: pre-wrap; font-size: 12px; }
</style>
<h1>Decode, paint, then check PNG access</h1>
<main id="cases"></main>
<pre id="report"></pre>
<script>
  function pngBlob(canvas) {
    return new Promise((resolve, reject) => {
      try { canvas.toBlob(blob => blob ? resolve(blob) : reject(new Error('Empty PNG')), 'image/png'); }
      catch (error) { reject(error); }
    });
  }
  function dataURL(blob) {
    return new Promise((resolve, reject) => {
      const reader = new FileReader();
      reader.onload = () => resolve(reader.result);
      reader.onerror = () => reject(reader.error);
      reader.readAsDataURL(blob);
    });
  }
  async function render(test) {
    const figure = document.createElement('figure');
    const canvas = document.createElement('canvas');
    canvas.width = 480;
    canvas.height = 320;
    const caption = document.createElement('figcaption');
    figure.append(canvas, caption);
    document.getElementById('cases').append(figure);
    const result = { id: test.id, url: new URL(test.src, location.href).href,
      crossOrigin: test.crossOrigin ?? null, canvasWidth: canvas.width, canvasHeight: canvas.height };
    const image = new Image();
    if (test.crossOrigin) image.crossOrigin = test.crossOrigin;
    image.src = result.url;
    try { await image.decode(); }
    catch (error) {
      result.stage = 'decode';
      result.error = error.name;
      caption.textContent = test.id + ': decode ' + error.name;
      return result;
    }
    result.naturalWidth = image.naturalWidth;
    result.naturalHeight = image.naturalHeight;
    const context = canvas.getContext('2d');
    context.drawImage(image, 0, 0, canvas.width, canvas.height);
    result.stage = 'painted';
    try {
      result.opening = [...context.getImageData(192, 160, 1, 1).data];
      result.fill = [...context.getImageData(192, 80, 1, 1).data];
      result.corner = [...context.getImageData(0, 0, 1, 1).data];
    } catch (error) { result.readbackError = error.name; }
    try {
      const blob = await pngBlob(canvas);
      result.pngType = blob.type;
      result.pngBytes = blob.size;
      result.pngDataURL = await dataURL(blob);
      result.export = 'ok';
      const download = document.createElement('a');
      download.href = result.pngDataURL;
      download.download = test.id + '.png';
      download.textContent = 'Save PNG';
      figure.append(download);
    } catch (error) { result.export = 'failed'; result.exportError = error.name; }
    caption.textContent = test.id + ': ' + result.stage + ', PNG ' + result.export;
    return result;
  }
  async function run() {
    const tests = [
      { id: 'same-origin', src: 'master.svg' },
      { id: 'cors-allowed', src: 'http://127.0.0.1:8783/cors/master.svg', crossOrigin: 'anonymous' },
      { id: 'no-cors-image', src: 'http://127.0.0.1:8783/plain/master.svg' },
      { id: 'cors-denied', src: 'http://127.0.0.1:8783/plain/master.svg', crossOrigin: 'anonymous' },
      { id: 'missing-file', src: 'missing.svg' },
    ];
    const results = [];
    for (const test of tests) results.push(await render(test));
    document.getElementById('report').textContent = JSON.stringify({ pageOrigin: location.origin, results }, (key, value) => key === 'pngDataURL' ? undefined : value, 2);
  }
  run();
</script>
</html>
```

書き出し関数は、投げられた例外と、nullのblob結果の両方を処理します。[Canvas.toBlob()](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob)はPNG出力に対応し、canvasがオリジンクリーンでない場合に`SecurityError`を投げることがあります。成功した二つのケースでは、PNG保存用リンクを追加します。表示される報告ではbase64の内容を省いています。

## SVGを変える前に失敗した段階を読む

このひな形をそのまま一つのChromeブラウザー環境で実行し、保存した二つのPNGを開き直しました。この観察は、自己完結した元のファイルと、この二つのローカルオリジンについてのものです。

| ケース                 | 画像リクエスト              | 観察した描画・読み取り・書き出し                         |
| ------------------- | -------------------- | ---------------------------------------- |
| 同一オリジン              | ローカルSVG、CORS属性なし     | 240 × 160でデコード、描画、読み取りとPNGが成功            |
| クロスオリジン、許可あり        | `anonymous`と応答ヘッダー   | デコード、描画、読み取りとPNGが成功                      |
| クロスオリジン、CORSリクエストなし | 属性なし、応答ヘッダーなし        | デコードと描画は成功、読み取りと書き出しが`SecurityError`を投げる |
| クロスオリジン、許可なし        | `anonymous`、応答ヘッダーなし | `EncodingError`でデコードが拒否され、描画は試行されない      |
| ファイルなし              | 同一オリジンの404           | `EncodingError`でデコードが拒否され、描画は試行されない      |

CORSなしのケースでも、アートワークは見えました。しかし、その見た目は読み取りや書き出しの許可を示していません。CORS拒否とファイルなしのケースは前の段階で失敗したため、形状を書き直しても、どちらのリクエスト失敗も解決しません。

二つの書き出しPNGは、どちらも`480 × 320`で開き直せました。この実行では、バイト単位で同一で、それぞれ10,509バイトでした。ビットマップ座標`(192,160)`の開口部は`RGBA (0,0,0,0)`で、角のアルファもゼロでした。`(192,80)`のティール色サンプルは`(23,107,91,255)`でした。これらのサンプルは、このファイルの開口部と塗りを確認するもので、すべての縁ピクセルやSVG機能を確認するものではありません。

ブラウザーページの白い長方形は、canvasの後ろのCSS背景です。PNGに加えた白い塗りではありません。不透明な背景が必要なら、画像を描く前に背景を明示的に描き、書き出したファイルをもう一度確認してください。

## 編集可能なマスターとPNGを分ける

保存したPNGはピクセルを含みます。元のパス、円、編集可能な内側の輪郭は持ちません。`master.svg`をベクターの元データとして保管し、実際にPNGが必要な利用先に合わせて、canvasのビットマップ寸法を選んでください。既存のPNGを後から拡大しても、元の形状は復元されません。

また、SVGというファイル名だけでは、中のすべてのアートワークがベクター形状である証明にはなりません。リンク画像や埋め込み画像を含む場合は、[埋め込みラスターの確認ガイド](https://perfectvector.com/ja/blog/svg-contains-raster-image)を使ってください。

元のアートワークがラスター画像でしか存在せず、編集可能な輪郭が必要な場合は、[PerfectVectorの画像からベクターへの手順](https://perfectvector.com/ja/image-to-vector)でSVGマスターを準備できます。この読み込み処理に使う前に、輪郭、色、開口部を確認してください。ベクター化はCORSヘッダーを修正したり、canvasの読み取り許可を与えたりしません。

## よくある質問

**SVGマークアップをdrawImage()に直接渡せますか？**
渡せません。この例は、SVGファイルをHTMLImageElementへ読み込み、デコードした画像をdrawImage()へ渡します。マークアップ文字列は、その画像オブジェクトではありません。ドキュメントの読み込みと、Path2Dのパスデータによる構築を分けてください。

**画像が表示されるのにPNGの書き出しが失敗するのはなぜですか？**
CORSの許可なしで読み込んだクロスオリジン画像は、描画後にcanvasを汚染する場合があります。このCORSなしのケースでは描画は成功しましたが、ピクセル読み取りと書き出しは両方ともSecurityErrorを投げました。SVG形状が壊れていると考えず、リクエストモードとサーバー応答を確認してください。

**crossOrigin anonymousはすべての画像URLを直しますか？**
直しません。srcより前に設定し、画像サーバーが互換性のあるCORS応答を提供する必要があります。同じアートワークでも、許可ヘッダーのないエンドポイントへanonymousを要求すると、デコードに失敗しました。

**canvasをPNGとして書き出すと編集可能なSVGパスが残りますか？**
残りません。PNGはラスター出力です。パス、図形、開口部を編集する必要があるなら、元のSVGを別に保管し、予定の納品サイズでPNGを確認してください。

## 出典

1. [MDN：CanvasRenderingContext2D.drawImage()](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/drawImage) — 対応する画像オブジェクト、出力先のサイズ、固有寸法。
2. [MDN：HTMLImageElement.decode()](https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/decode) — 画像準備のPromiseと、リクエストまたはデコードの拒否。
3. [MDN：CORS対応画像](https://developer.mozilla.org/en-US/docs/Web/HTML/How_to/CORS_enabled_image) — 描画画像の読み取りと書き出しに対するオリジンクリーンの制約。
4. [MDN：HTMLImageElement.crossOrigin](https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/crossOrigin) — 画像読み込みのCORSリクエストモード。
5. [MDN：HTMLCanvasElement.toBlob()](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob) — PNG blobの生成、nullの出力、SecurityError。
6. [MDN：画像としてのSVG](https://developer.mozilla.org/en-US/docs/Web/SVG/Guides/SVG_as_an_image) — SVGの画像コンテキストと外部リソースの制約。

***

ラスターの元画像から、編集可能なアートワークを作りたいですか。[PerfectVectorでSVGマスターを作成](https://perfectvector.com/ja/image-to-vector)し、輪郭と開口部を確認してください。そのマスターを保管し、canvas描画と書き出したPNGを別々に確認します。

## Explore

- [Read the HTML article](https://perfectvector.com/ja/blog/svg-canvas-image-export)
- [MapLibreのSVGアイコン：ピクセルとシンボルサイズの確認](https://perfectvector.com/ja/blog/maplibre-svg-symbol-image.md)
- [p5.jsのSVG画像：読み込み、拡大縮小、元データの保管](https://perfectvector.com/ja/blog/p5js-svg-artwork.md)
- [Product facts](https://perfectvector.com/product-facts.md)
- [FAQ](https://perfectvector.com/faq.md)
- [Pricing and credits](https://perfectvector.com/pricing.md)
