CODE HOUSE


Hello, I'm a freelance software engineer.


透過pngをsvgに変換する

透過PNGをSVG画像に変換する。

絵が下手すぎる・・・

pngをsvgに変換する。世の中そんなツールは出回ってそうなので簡単にできるかと思っていたが、以外にもハマりどころが多かったのでメモ。前提として、下記のことを達成することを目的とする。

  • 元になるpng画像はモノトーンの透過画像である。
  • png画像は、あるフォルダの複数のサブフォルダの中に複数存在する。
  • svg画像は別のフォルダに出力する。この時元のフォルダの構造をそのまま維持する。
  • 上記をshell scriptでちゃちゃっと実行する。

ハマったこと

  • findでファイルを探すもファイルが多い場合。
  • xargsでshell functionはそのままでは実行できない。
  • folder構造を維持するには?
  • 透過pngをsvgに変換する方法
  • osxで\を入力しようとしたら¥になる?

findでファイルを探すもファイルが多い場合。

例えば、shellでfind . -name '*.png'のように実行すると、カレントフォルダ以下にあるpngファイルが検索できる。これをパイプを使って次のコマンドに渡したりできるのだが、ファイル数が多い場合エラーになってしまう。そこで、fineの結果をxargsというコマンドに渡して、どんなにファイルが多くても1つずつ処理させたりすることができる。(必ずしも1つずつ処理させる必要はないが)

1find . -name '*.png' | xargs -IX -n1 wc -c X
  • findの結果を1つずつ処理するために、xargsで-n1を指定している。こうすることでxagrsに続くコマンドに1つだけ値を渡すことができる。
  • -n1は次のコマンドにパラメータを幾つずつ渡すかを指定するパラメータ。次のコマンドを同時に幾つ実行するかは別途指定する必要がある。

xargs を使ってカジュアルに並列処理 - たごもりすメモ

  • 上記の例では、xargsからwcコマンドを実行している。wc -c filenameで、ファイルのサイズとパスが表示される。
  • xargsに続くコマンドにパラメータを渡す場合、そのコマンドの第一引数にパラメータを渡す場合は、xargs -n1 wcのようにすればいい。しかし、そうではない場合、xargsの-Iオプションを使用する。
  • -Iに続けて任意の文字を指定する(上の例ではX)。そしてwcコマンドのパラメータを受け取る位置にもその任意の文字を書く。

folder構造を維持するには?

フォルダの構造を維持して、svgファイルを出力したい。そこで、あるフォルダのファイルを最初に処理した際に、出力先にも同じフォルダを作成するshell functionを作ってみる。

pngtosvg.sh
 1# $1に元ファイルのパス、$2に出力先のフォルダ名が指定されているとする
 2function pngtosvg() {
 3  src=$(dirname $1)
 4  exp="s/^[^\/]*/$2/"
 5  dist=`echo $src | sed -e $exp`
 6
 7  if [ ! -d $dist ]; then
 8    echo $dist
 9    mkdir -p $dist
10  fi
11}

このfunctionのハマりポイント。

  • 変数に値を代入する場合、=の前後にスペースを開けてはいけない。
    • スペースを開けると、変数名をコマンド名と解釈してしまいエラーになる。
  • sedの正規表現では、最短一致の?が使用できない。上記の例では、元フォルダの一番親のフォルダ名を$2で指定されたフォルダ名に変換している。
    • ?が使えるなら、s/^.*?/\/$2/の様に指定できるはずである。
  • mkdirでフォルダを作成する場合、途中のフォルダが存在しない場合、通常であればエラーになる。-pオプションを付ければ、途中のフォルダも全て作成してくれる。

xargsでshell functionはそのままでは実行できない。

上記のshell functionをxagrsから実行しようとしたところ、そのまま実行できなくてハマった。調べてみると、下記のような記事が見つかった。

xargsにbashのfunctionを渡す方法 - Weballergy

記事に書いてある通りに、shell functionをexportして、xargsからはbashのスクリプトとして実行する。

pngtosvg.sh
 1# $1に元ファイルのパス、$2に出力先のフォルダ名が指定されているとする
 2function pngtosvg() {
 3  src=$(dirname $1)
 4  exp="s/^[^\/]*/$2/"
 5  dist=`echo $src | sed -e $exp`
 6
 7  if [ ! -d $dist ]; then
 8    echo $dist
 9    mkdir -p $dist
10  fi
11}
12export -f pngtosvg
13
14# ここの$1はpngtosvg.shに渡された第一パラメータ(元フォルダ)である。$2は出力先のフォルダである。
15find $1 -name '*.png' | xargs -IX -n1 bash -c "pngtosvg X $2"
  • sh pngtosvg.sh hoge fugaの様に実行する。hogeのフォルダ構造がそのままfugaフォルダにも適応される。
    • ただし、pngファイルの存在するフォルダのみが適応される。
  • 見つかったpngファイルが全てxargsに渡される。
  • xargsからはbash経由でconvにXと$2が渡される。Xは見つかったpngファイルである。

透過pngをsvgに変換する方法

画像をsvgに変換するツールはpotraceを利用する。OSXの場合brew install potraceで簡単にインストールができた。

Peter Selinger: Potrace

しかし、このpotrace。いろいろと制限があり結構ハマった。

  • 変換元のファイルとして直接pngを指定することはできない。
    • 変換元としてはいくつかあるようだが、bmpを使ってみることにする。
  • pngをbmpにするツールはたくさんある。しかし、透過のpngを透過でないbmpに変換しないと、結果のsvgファイルが正しく作成されない。

透過pngを透過でないbmpに変換するにあたっては、下記が参考になった。

(Qiita)コマンド一発で透過 png を綺麗に非透過 png にする

上記の記事で使われているconvertというコマンドは、imagemagickをインストールすると使えようになる。imagemagickもOSXの場合brew install imagemagickでインストールが可能である。上記の記事ではpngをpngに変換しているが、この記事ではbmpに変換するので、下記のように最後のファイル名を.bmpに変更する。

1convert xxx.png  \( +clone -alpha opaque -fill white -colorize 100% \) +swap -geometry +0+0 -compose Over -composite -alpha off xxx.bmp
2potrace --svg xxx.bmp

osxで\を入力しようとしたら¥になる?

上記のコマンドで、\を入力しようとしたところ、OSXの環境によっては¥しか入力できなくてハマった。 対処の方法は下記のようなものがある。

  • option + \\が入力できる。
  • システム環境設定 > キーボード > 入力ソースで、¥キーで入力する文字\に変更する。

shell script最終版

以上をふまえて、最終的なshell scriptは下記のようになる。

pngtosvg.sh
 1# $1に元ファイルのパス、$2に出力先のフォルダ名が指定されているとする
 2function pngtosvg() {
 3  base=$(basename $1)
 4  src=$(dirname $1)
 5  exp="s/^[^\/]*/$2/"
 6  dist=`echo $src | sed -e $exp`
 7  distfile=`echo $dist/$base | sed -e 's/.png/.bmp/'`
 8
 9  if [ ! -d $dist ]; then
10    echo $dist
11    mkdir -p $dist
12  fi
13
14  convert $1  \( +clone -alpha opaque -fill white -colorize 100% \) +swap -geometry +0+0 -compose Over -composite -alpha off $distfile
15  potrace --svg $distfile
16  rm $distfile
17}
18export -f pngtosvg
19
20# ここの$1はpngtosvg.shに渡された第一パラメータ(元フォルダ)である。$2は出力先のフォルダである。
21find $1 -name '*.png' | xargs -IX -n1 bash -c "pngtosvg X $2"