CLIでSVGのiOSアプリアイコンをPNGへ（高速に）エクスポートする


iOSアプリのアプリアイコン用に作ったSVG形式のファイルをInkscapeを使ってPNGへエクスポートすることがあります．
その際，様々なサイズにエクスポートする必要があるのでシェルスクリプトを作ってあるのですが，Inkscapeの起動処理が若干遅く変換に10秒ほどかかっていたので，Inkscapeをシェルモードで動かし標準入力から変換コマンドを送る方法で高速化してみました．

## 環境

* macOS Sierra 10.12.6
* bash 3.2
* Inkscape 0.48.5

## 普通にInkscapeをCLIから起動してPNGエクスポートする方法

次のようなコマンドで変換が可能です．

```bash:
$ /Applications/Inkscape.app/Contents/Resources/bin/inkscape --file=app-icon.svg --export-png=app-icon/icon-20.png --width=20 --height=20
```

1件だけならこれでよいのですが，毎回Inkscapeのプロセスを起動すると時間がかかってしまいます．

## シェルモードのInkscapeでPNGエクスポートする方法

Inkscapeにはシェルモードというものがあり，`--shell`オプションで起動するとシェルモードになります．
シェルモードでは次のように変換が可能です．

```bash:
$ /Applications/Inkscape.app/Contents/Resources/bin/inkscape --shell
W: AppleCollationOrder setting not found, using AppleLocale.
Setting LANGSTR from AppleLocale: en
Overriding empty LANG from /usr/share/locale/locale.alias
Setting Language: en_US.UTF-8
Inkscape 0.48.5 r10040 interactive shell mode. Type 'quit' to quit.
>app-icon.svg --export-png=app-icon/icon-20.png --export-width=20 --export-height=20
Background RRGGBBAA: ffffff00
Area 0:0:512:512 exported to 20 x 20 pixels (3.51562 dpi)
Bitmap saved as: app-icon/icon-20.png
>quit
```

`app-icon.svg --export-png=app-icon/icon-20.png --export-width=20 --export-height=20`と`quit`をキーボードで入力しています．

## スクリプトからシェルモードのInkscapeへコマンドを送る

このシェルモードでInkscapeをバックグラウンドで動かしつつ，Inkscapeの標準入力へコマンドを送るために，名前付きパイプを作成しプロセス間通信をするやり方が以下です．

```bash:
$ mkfifo inkscape-pipe
$ /Applications/Inkscape.app/Contents/Resources/bin/inkscape --shell < inkscape-pipe &
[3] 30827
$ echo app-icon.svg --export-png=app-icon/icon-20.png --export-width=20 --export-height=20 > inkscape-pipe
$ W: AppleCollationOrder setting not found, using AppleLocale.
Setting LANGSTR from AppleLocale: en
Overriding empty LANG from /usr/share/locale/locale.alias
Setting Language: en_US.UTF-8
Inkscape 0.48.5 r10040 interactive shell mode. Type 'quit' to quit.
>Background RRGGBBAA: ffffff00
Area 0:0:512:512 exported to 20 x 20 pixels (3.51562 dpi)
Bitmap saved as: app-icon/icon-20.png
>
[3]  + done       /Applications/Inkscape.app/Contents/Resources/bin/inkscape --shell <
$ rm inkscape-pipe
```

`inkscape-pipe`という名前付きパイプファイルを作成し，それを通じてシェルモードのInkscapeへコマンドを送っています．

## アプリアイコン作成の自動化

この仕組みを使ってアプリアイコンの自動化を行ったスクリプトが以下です．
[GitHubにもコミットしてあります](https://github.com/ottijp/dotfiles/blob/master/bin/inkscape-export-ios.sh)．

```bash:inkscape-export-ios.sh
#!/bin/bash
set -eu

readonly INKSCAPE_PATH=/Applications/Inkscape.app/Contents/Resources/bin/inkscape

if [ $# -lt 1 ]; then
  >&2 echo "usage: $0 <svg-file-path>"
  exit 1
fi

PNG_PATH=${1%.*}
echo "export to $PNG_PATH"

if [ ! -f "$INKSCAPE_PATH" ]; then
  >&2 echo "inkscape not found: $INKSCAPE_PATH"
  exit 1
fi

if [ ! -f "$1" ]; then
  >&2 echo "file not found: $1"
  exit 1
fi

mkdir -p "$PNG_PATH"

TMP_REQ_DIR=`mktemp -d`
TMP_PIPE_FILE="$TMP_REQ_DIR/pipe"
mkfifo "$TMP_PIPE_FILE"

# inkscakepをバックグラウンドで起動し，リクエストをパイプ経由で渡す
"$INKSCAPE_PATH" --shell < "$TMP_PIPE_FILE" &
INKSCAPE_PID=$!

# inkscapeシェルへのリクエストを構築
cat <<-EOF > "$TMP_PIPE_FILE"
"$1" --export-png="$PNG_PATH/icon-20.png" --export-width=20 --export-height=20
"$1" --export-png="$PNG_PATH/icon-20@2x.png" --export-width=40 --export-height=40
"$1" --export-png="$PNG_PATH/icon-20@3x.png" --export-width=60 --export-height=60
"$1" --export-png="$PNG_PATH/icon-29.png" --export-width=29 --export-height=29
"$1" --export-png="$PNG_PATH/icon-29@2x.png" --export-width=58 --export-height=58
"$1" --export-png="$PNG_PATH/icon-29@3x.png" --export-width=87 --export-height=87
"$1" --export-png="$PNG_PATH/icon-40.png" --export-width=40 --export-height=40
"$1" --export-png="$PNG_PATH/icon-40@2x.png" --export-width=80 --export-height=80
"$1" --export-png="$PNG_PATH/icon-40@3x.png" --export-width=120 --export-height=120
"$1" --export-png="$PNG_PATH/icon-60@2x.png" --export-width=120 --export-height=120
"$1" --export-png="$PNG_PATH/icon-60@3x.png" --export-width=180 --export-height=180
"$1" --export-png="$PNG_PATH/icon-76.png" --export-width=76 --export-height=76
"$1" --export-png="$PNG_PATH/icon-76@2x.png" --export-width=152 --export-height=152
"$1" --export-png="$PNG_PATH/icon-83.5@2x.png" --export-width=167 --export-height=167
"$1" --export-png="$PNG_PATH/icon-1024.png" --export-width=1024 --export-height=1024
quit
EOF

wait $INKSCAPE_PID
echo done

rm -r $TMP_REQ_DIR
```

## シェルモードと非シェルモードの速度比較

非シェルモードで逐一Inkscapeのプロセスを起動するスクリプト（[inkscape-export-ios-noshellmode.sh](https://gist.github.com/ottijp/f19f5db3cd0ccda096f977eeaf285767)）と速度比較してみました．

```bash:
$ time inkscape-export-ios.sh app-icon.svg
export to app-icon
...（中略）
done
inkscape-export-ios.sh app-icon.svg  0.64s user 0.34s system 92% cpu 1.059 total


$ time inkscape-export-ios-nonshellmode.sh app-icon.svg
export to app-icon
...（中略）
done
inkscape-export-ios-noshellmode.sh app-icon.svg  6.48s user 5.29s system 94% cpu 12.497 total
```

12.5秒→1.1秒と大幅に高速化できました．
エクスポートするファイル数が多いほどもっと効果が出ると思います．