SVGを画像化する

はじめに

昨今のクライアントサイドでは、動的な画像のレンダリング、アニメーション、拡張・縮小を求められることが多々あります。そのような際にSVGは利用しやすい形式です。一方で、画像として内容を保存したくなることもあります。そのような場合に使える、SVG画像をPNG画像に変換する方法を簡単に述べます。

SVGとは

SVGファイル(スケーラブル・ベクター・グラフィックス、Scalable Vector Graphics)は画像形式の1つです。XMLをベースにした二次元ベクターデータで画像を描きます。ベクターデータとは「画像を、点の座標とそれを結ぶ線(ベクター、ベクトル)などの数値データをもとにして演算によって再現する方式」です。その大きな特徴は「拡大・縮小しても画質が損なわれない」ことです。

Illastratorなどでも作れますが、JavaScriptで簡単に作れます。実際にSVGを業務などでがっつりと利用するのであれば、d3jsがお勧めです。かなり扱いやすくできています。

実際の変換処理

やり方は非常に簡単で、以下の3ステップで可能です。

  1. SVG画像を作成する
  2. XMLSerializerを使ってSVG画像のデータを取り出す
  3. Canvasを使ってPNG形式に変換する

XMLSerializerは最新のブラウザであればほぼ使用可能です(詳細はこちら)。

コード

実際のコードは次のようになります。svg2jpeg関数にSVG要素(DOM)を渡すことで、PNG画像に変換します。変換処理では、SVG要素と同じサイズのCanvasを使い、Imageオブジェクトを利用してSVGデータをCanvasに貼り付けます。その後、CanvasのtoDataURL()メソッドを使用してPNG画像データを取り出します。

function svg2jpeg(svgElement, sucessCallback, errorCallback) {
  var canvas = document.createElement('canvas');
  canvas.width = svgElement.width.baseVal.value;
  canvas.height = svgElement.height.baseVal.value;
  var ctx = canvas.getContext('2d');
  var image = new Image;
  
  image.onload = () => {
    // SVGデータをPNG形式に変換する
    ctx.drawImage(image, 0, 0, image.width, image.height);
    sucessCallback(canvas.toDataURL());
  };
  image.onerror = (e) => {
    errorCallback(e);
  };
  // SVGデータを取り出す
  var svgData = new XMLSerializer().serializeToString(this.damageMap.nativeElement);
  image.src = 'data:image/svg+xml;charset=utf-8;base64,' + btoa(svgData);
}
// 使い方
svg2jpeg(document.getElmentById('SVG要素のID'), function(data) {
    // data: JPEGのbase64形式データ(文字列)
}, function(error) {
    // error: 何らかのエラーオブジェクト  
})

HTMLサンプル

実際に試すには、以下のコードをHTMLファイルに保存してブラウザで開いて見てください。「変換する」ボタンを押すことで、SVGをPNG画像に変換し、表示します。Chrome、Firefox、Safariでは動作確認済みです。

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>Sample</title>
  <style>
    .image {
      margin: 0 auto;
      display: flex;
      align-items: center;
      width: 900px;
    }
    .image__svg {
      width: 400px;
    }
    .image__sep {
      flex-grow: 1;
      text-align: center;
    }
    .image__converted {
      width: 400px;
      height: 400px;
      border: 1px solid rgba(0, 0, 0, 0.5);
    }
  </style>
</head>
<body>
<div class="image">
  <div class="image__svg">
    <svg
        id="svg"
        xmlns="http://www.w3.org/2000/svg"
        width="400"
        height="400"
        viewBox="0 0 400 400"
        preserveAspectRatio="xMidYMid meet"
        style="background-color: #eee;">
      <circle cx="50" cy="50" r="50" fill="rgba(255,0,0,0.5)" />
      <circle cx="200" cy="200" r="50" fill="rgba(0,255,0,0.5)" />
      <circle cx="350" cy="350" r="50" fill="rgba(0,0,255,0.5)" />
      <circle cx="350" cy="50" r="50" fill="rgba(255,255,0,0.5)" />
      <circle cx="50" cy="350" r="50" fill="rgba(0,255,255,0.5)" />
    </svg>
  </div>
  <div class="image__sep">
    <div>==&gt;&gt;</div>
    <div><button id="convert-button">変換する</button></div>
  </div>
  <div class="image__converted">
    <img src="" id="converted-image">
  </div>
</div>
<script>
  (function() {
    document.addEventListener('DOMContentLoaded', function() {
      document.getElementById('convert-button').addEventListener('click', function() {
        svg2imageData(document.getElementById('svg'), function(data) {
          console.log(data);
          document.getElementById('converted-image').src = data;
        }, function(error) {
          console.log(error);
          alert('failed to convert');
        });
      });
    });
    function svg2imageData(svgElement, successCallback, errorCallback) {
      var canvas = document.createElement('canvas');
      canvas.width = svgElement.width.baseVal.value;
      canvas.height = svgElement.height.baseVal.value;
      var ctx = canvas.getContext('2d');
      var image = new Image();
      image.onload = () => {
        ctx.drawImage(image, 0, 0, image.width, image.height);
        successCallback(canvas.toDataURL());
      };
      image.onerror = (e) => {
        errorCallback(e);
      };
      var svgData = new XMLSerializer().serializeToString(svgElement);
      image.src = 'data:image/svg+xml;charset=utf-8;base64,' + btoa(svgData);
    }
  }());
</script>
</body>
</html>

実際にブラウザで表示すると次のように表示されます。

f:id:asialkazushi:20180914141741p:plain
初期画面

変換ボタンをクリックすると、右側の領域にPNG画像が表示されます。開発ツールなどで確認してください。

f:id:asialkazushi:20180914141746p:plain
変換後

おわりに

画像や何らかのグラフ表示などの課題に直面した際には、SVGとCanvasを使えればほぼ解決可能です。画像化の方法を組み合わせることで、サーバへ保存できるようにもなり、なお便利になります。