svg.jsとcanvgを使って、SVGタグ内のオブジェクトをクライアント側だけで画像化させてみる

この記事は「[SVG Advent Calendar 2014](http://www.adventar.org/calendars/366)」の8日目の記事です。前日の記事も私が書いてまして、「[svg.jsとsvg.draggable.jsを使って、SVGタグ内のオブジェクトをドラッグ＆ドロップさせてみる](http://qiita.com/akase244/items/4480ca2869e3e48324d6)」としてまとめています。

この記事では、サーバーを挟まずクライアント側の処理だけで、SVGタグを画像化させるということを実現しています。


やり方は簡単で[canvg](https://github.com/gabelerner/canvg)を使います。canvgはsvgをパースしcanvas化することができるツールです。canvas化してしまえば「toDataURL()」が使えるので、クライアント側だけで処理が完結するという仕組みです。


実際のコードはこんな感じで、canvg関数にcanvasのDOMオブジェクトとsvgタグを引数として渡し、コールバック関数(renderCallback)の中で、canvasのtoDataURL()を呼び出すだけです。カンタン！！（※もちろん、svg.jsとjQueryは使わなくても書けますよ。）

```html:HTML
<head>
    <!-- build:js(.) scripts/vendor.js -->
    <!-- bower:js -->
    <script src="bower_components/jquery/dist/jquery.js"></script>
    <script src="bower_components/svg.js/dist/svg.js"></script>
    <script src="bower_components/canvg/dist/canvg.js"></script>
    <!-- endbower -->
    <!-- endbuild -->
</head>
<body>
    <svg id="drawing" style="border:solid 1px;"></svg>

    <div><button id="button">convert to image</button></div>

    <script>
        var draw = SVG('drawing').size(500, 500);
        var image = draw.image('images/NKJ56_gatsudanshi500.jpg', 150, 100).dx(30).dy(30);
        var text = draw.text('SVGを画像化できますか？').move(150, 150).fill('#f06');
        var rect = draw.rect(100, 100).attr({fill: '#eee'}).dx(200).dy(200);
        var circle = draw.circle(100).attr({fill: '#ff0'}).dx(300).dy(300);

        $('#button').on('click', function () {
            var svgTag = $('svg').get(0).outerHTML;
            var canvas = $('<canvas>').get(0);
            canvg(canvas, svgTag, {
                renderCallback: function() {
                    $('body').find('.convImg').remove(); 
                    $('body')
                        .append($('<img>')
                        .css('border', 'solid 1px')
                        .addClass('convImg')
                        .attr('src', canvas.toDataURL())); 
                },
            }); 
        });
    </script>
</body>
```

ただし、これを実行してみると分かりますが、残念ながら画像が粗くなってしまいます。
canvgのドキュメントを見てみても、クオリティやスムージングといったキーワードが見つからず、こんなものなのかと嘆いています。

---

#### このようなsvgタグが
![before.PNG](https://qiita-image-store.s3.amazonaws.com/0/23093/829e691b-bdd2-a8a9-36c2-6f07f782a301.png)

---

#### 画像化されました。が、粗い。。。フォントもなんかジャギってる。つーか、文字自体が小さくなってる。。。
![after.PNG](https://qiita-image-store.s3.amazonaws.com/0/23093/0cb4548e-56ed-ac9a-9809-f37cd339b682.png)


ちなみにこのコード、Chromeで動作確認を取りましたが、IEでは動いてくれませんでした。色々調べてみるとIEでは一工夫必要なようですので、ぜひ調べてみてください。

で、画像の粗さについて、もうちょっと改善する方法がないものか調べてみたところ、canvgにはcanvasの「[drawImage](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D.drawImage)」と似た「drawSvg」という関数が準備されていることが分かりました。
canvasのgetContextの際に、「[imageSmoothingEnabled](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D.imageSmoothingEnabled)」というパラメータが渡せるので、これで少しは違うかもしれないと思い試してみたところ。。。残念ながら結果は変わらず。（どうやらまだexperimentalな仕様のようですね）

他にもsvgの「[shape-rendering](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/shape-rendering)」やcanvasの「[image-rendering](https://developer.mozilla.org/ja/docs/Web/CSS/image-rendering)」といったパラメータがあるので、この辺を組み合わせるともしかしたら改善するかもしれないです。が、今回は未調査。

```html:HTML
<body>
    <svg id="drawing" style="border:solid 1px;"></svg>

    <div><button id="button">convert to image</button></div>

    <script>
        var draw = SVG('drawing').size(500, 500);
        var image = draw.image('images/NKJ56_gatsudanshi500.jpg', 150, 100).dx(30).dy(30);
        var text = draw.text('SVGを画像化できますか？').move(150, 150).fill('#f06');
        var rect = draw.rect(100, 100).attr({fill: '#eee'}).dx(200).dy(200);
        var circle = draw.circle(100).attr({fill: '#ff0'}).dx(300).dy(300);

        $('#button').on('click', function () {
            var svgTag = $('svg').get(0).outerHTML;
            var $canvas = $('<canvas>').attr('width', '500px').attr('height', '500px').addClass('convCanvas');
            var context = $canvas.get(0).getContext('2d');
            context.imageSmoothingEnabled = true;
            context.drawSvg(svgTag, 0, 0);
            $('body').find('.convCanvas').remove(); 
            $('body').append($canvas); 
       });
    </script>
</body>
```

 - 今回の検証に際し利用したもの
   - Yeomanジェネレータの[generator-webapp](https://github.com/yeoman/generator-webapp)（検証用HTMLのひな形作成）
   - Bower（svg.jsとcanvgのインストール）
   - PAKUTASOの[つよぽん画像](http://www.pakutaso.com/web_tsuyoshi.html)(［ photo by [pakutaso.com](http://www.pakutaso.com/) ］)


 - 参考URL
   - [D3.jsで作成したグラフ(SVG)を画像として保存する](http://tech-sketch.jp/2013/10/d3js-svg-convert-to-png.html)
   - [JAVASCRIPTでSVGをラスター画像に変換](http://curtaincall.weblike.jp/blog/?p=174)
   - [CANVGをIEで利用するときの注意点](http://curtaincall.weblike.jp/blog/?p=183)
   - [HTML中に埋め込まれたSVGをファイル／データとして抽出する](http://qiita.com/norami_dream/items/8751708e49a66f6352b8)
   - [D3.jsなどで出力されるsvgを画像として表示する方法](http://qiita.com/okappy/items/bc20774aa08842adf4d7)
   - [SVG を PNG に変換する](http://daily.d3js.info/?p=339)
   - [SVGをCavnasで利用する方法](http://nmi.jp/archives/223)
   - [btoa base64](https://gist.github.com/franklinjavier/5333913)