Node.jsでInkscapeを子プロセスとして動かしてSVGをPNGに変換する（svg2pngとの速度比較も）	


Node.js（CoffeeScript）でSVGファイルをPNGに変換する必要があって，Inkscapeを子プロセスで走らせて変換するように実装してみました．

また，はじめは[svg2png](https://www.npmjs.com/package/svg2png)などのライブラリを使ってやってみましたが，単純なSVGファイルだと変換速度が遅かったのでInkscapeでの実装に切り替えました．その2つの実装の速度比較もしてみました．

## 環境
* Mac mini (Mid 2011) CPU2.5GHz, Memory16GB
* Node.js v5.11.0
* Inkscape 0.48.5
* npmライブラリ
    * coffee-script 1.10.0
    * svg2png 3.0.1
    * pn 1.0.0
    * q 1.4.1

## サンプルSVGファイル
* 単純なSVG: [color_circles.svg](https://gist.github.com/ottijp/69a15e68eac7704acc9a7ff7246bbf50)（3色のカラーサークル）
* 複雑なSVG: [cat.svg](https://gist.github.com/ottijp/a5836c0c529b3acee40b5e71ddbfd22d)（[こちら](https://flic.kr/p/wfEp7x)をビットマップトレースしたもの）

## svg2pngを使った場合の変換
``` svg2png_convert.coffee
fs = require "pn/fs"
svg2png = require "svg2png"
debug = require("debug")("svgtest")
Q = require "q"
 
filename = process.argv[2]
cycle = process.argv[3]
tasks = [1..cycle]
        
# pvg2pngを使って変換するファンクション
convert = (index) ->
  debug "start to convert", index
  fs.readFile filename
  .then svg2png
  .then (buffer) ->
    fs.writeFile "#{filename.replace(/\..+$/, '')}.png", buffer
  .then ->
    debug "convert finished", index

# cycleの数だけ順次繰り返す
tasks.reduce (prev, cur, index) ->
  prev.then ->
    Q.fcall convert, index
, Q()
.catch (e) ->
  console.error e
.done ->
  console.log "finish"

```

``` bash
$ npm install svg2png
$ npm install pn
$ npm install q
$ DEBUG=svgtest coffee svg2png_convert.coffee color_circles.svg 1
  svgtest start to convert +0ms 0
  svgtest convert finished +1s 0
finish
```

## Inkscapeを使った場合の変換
``` inkscape_convert.coffee
inkscapeHelper = require "./inkscape_helper"
debug = require("debug")("svgtest")
Q = require "q"

filename = process.argv[2]
cycle = process.argv[3]
tasks = [1..cycle]

# Inkscapeを使って変換するファンクション
convert = (index) ->
  debug "start to convert", index
  Q.nfcall inkscapeHelper.convert,
    filename, "#{filename.replace(/\..+$/, '')}.png"
  .then ->
    debug "convert finished", index

# cycleの数だけ順次繰り返す
Q.nfcall inkscapeHelper.open
.then ->
  debug "opened"
  tasks.reduce (prev, cur, index) ->
    prev.then ->
      Q.fcall convert, index
  , Q()
.then ->
  inkscapeHelper.close()
.catch (e) ->
  console.error e
.done ->
  console.log "finish"
```

``` inkscape_helper.coffee
spawn = require("child_process").spawn
Q = require "q"

 # Inkscape実行ファイルへのパスは環境によって読み替えてください
INKSCAPE_PATH = "/Applications/Inkscape.app/Contents/Resources/bin/inkscape"
inkscape = null
completion = null

# 子プロセスの起動
exports.open = (cb) ->
  inkscape = spawn INKSCAPE_PATH, ["--shell"]
  completion = cb

  inkscape.stdout.on "data", (data) ->
    # console.log data.toString('utf-8')
    if (data.toString().search /(\r\n?)?>$/) isnt -1
      # Inkscapeのshellモードで入力プロンプトが表示された
      if completion?
        completion()
        completion = null

  inkscape.stderr.on "data", (data) ->
    console.error "INKSCAPE ERROR", data.toString('utf-8')

  inkscape.on "close", (code) ->
    console.log "INKSCAPE EXITED", code

# 変換処理
exports.convert = (svgPath, pngPath, cb) ->
  completion = cb
  inkscape.stdin.write "#{svgPath} \
    --export-png=#{pngPath} --export-background-opacity=0 \r\n"

# 子プロセスの終了
exports.close = ->
  inkscape.stdin.write "quit\r\n"
```

``` bash
$ npm install q
$ DEBUG=svgtest coffee inkscape_convert.coffee color_circles.svg 1
INKSCAPE ERROR W: AppleCollationOrder setting not found, using AppleLocale.

INKSCAPE ERROR Setting LANGSTR from AppleLocale: en

INKSCAPE ERROR Overriding empty LANG from /usr/share/locale/locale.alias

INKSCAPE ERROR Setting Language: en_US.UTF-8

INKSCAPE ERROR 
(process:54607): Gtk-WARNING **: Locale not supported by C library.
	Using the fallback 'C' locale.

  svgtest opened +0ms
  svgtest start to convert +3ms 0
  svgtest convert finished +155ms 0
finish
INKSCAPE EXITED 0
```

* Inkscapeを`--shell`でシェルモード起動し，標準入出力で会話しています
* シェルモードでの変換命令はサイズ変更なども可能ですが，ここでは単にSVGのドキュメントサイズでSVGからPNGへの変換のみを行っています．その他使えるオプションは[Inkscape コマンドライン マニュアル](http://www.hcn.zaq.ne.jp/___/unix/inkscape-man-ja.html)を参照してください
* 変換完了はInkscapeから`> `のプロンプトが標準出力に出たことで判断しています
* 起動時にInkscapeからワーニング出てますが無視してます・・・


## 速度比較

svg2pngとInkscapeで変換をした時の速度比較をしてみます．（サンプルSVGファイルの2つについてそれぞれ50回変換）

``` bash
$ time DEBUG=svgtest coffee svg2png_convert.coffee color_circles.svg 50
$ time DEBUG=svgtest coffee svg2png_convert.coffee cat.svg 50
$ time DEBUG=svgtest coffee inkscape_convert.coffee color_circles.svg 50
$ time DEBUG=svgtest coffee inkscape_convert.coffee cat.svg 50
```

### 結果（timeコマンドのrealの値）

| 実装      | color_circles.svg | cat.svg
|:---------|------------------:|------------:|
| svg2png  | 13.186s | 76.624s
| Inkscape | 7.487s | 78.413s
            
複雑なSVGだとそんなに違いはでませんでしたが，単純なSVGだとInkscapeの方が早い時間で変換できました．svg2pngの内部ではPhantomJS（ヘッドレスブラウザ）を使っているようなので，その関係で一定以下にはならないんでしょうか？（確証はありません）

今回私が必要だったのは，単純なSVGファイルを大量にPNG化する処理だったので，Inkscapeを使った変換に切り替えることで処理速度を向上させることができました．