sng(PNGのマークアップ言語SNG用コンパイラ)をWindowsに仮移植し、MinGWでビルドする

id:eel3:20120615:1339768796 でzlibをビルドし、id:eel3:20120616:1339815893 ではlibpngをビルドした。全てはWindowssngを使う為に……ソ○モンよ! 私は帰って来た!

SNG(Scriptable Network Graphics)というマークアップ言語というかミニ言語がある。PNG(Portable Network Graphics)ファイルの内部構造をテキストで表現する為の言語で、概ねPNGのチャンク構造をCSSに似たスタイルで記述した感じになる。

このSNGを取り扱うための処理系がsngで、SNGからPNGエンコードしたり、その逆にPNGをデコードしてSNGを吐き出したりすることができる。作者は知る人ぞ知るESRだったりする。

仕事でPNGの画像ファイルを加工することになり、急遽インストールしたフリーのペイントツールで元のファイルを大きくした新しいファイルを作成したら何故かファイルサイズが9分の1近く小さくなった。原因を調査するためにPNGファイルの中身を比較したくなった。そこで思いついたのが「PNGをSNGに変換して比較する」という方法だった*1

しかしsngはマイナーな為か探し方が悪かったからかWindows用のバイナリは見つからず、MinGW等でビルドした云々の情報も引っかからなかった。DebianUbuntuリポジトリには入っているようだ。

もしかしてUnixシステムコールでも使っているのだろうかと疑いつつ、しかしESRのことだからC言語で書くなら概ねANSI Cの範囲で実装しているだろう*2という直感があった。PNGの構造やSNGのフォーマットを見た分には、仮に自分が実装する場合もANSI Cの範疇で何とかなりそうな気がした。なので運がよければMinGWでそのままビルドできて、そうじゃなかったとしてもあまり苦労せずに移植できるのではないかと考えて、Windows用実行ファイルの生成に挑戦してみた。

使用した処理系は直前に書いたようにMinGW(正確にはTDM-GCC 4.5.1)。zlibやlibpngをビルドしたのと同じコンパイラだ。MinGWUnix環境固有のライブラリ関数の一部をサポートしていて、それらの関数の代替案を考える必要がなくなるので少しだけ作業が楽になる。

入手するもの

ソースファイルはhttp://sourceforge.net/projects/sng/から入手する。現時点の最新版はsng-1.0.5.tar.gzだ。

依存関係の都合でlibpngとzlib。sngはlibpngを使用していて、libpngはzlibを使用している。入手とビルドについては前のエントリで書いているので割愛する。

もう1つ、sngはX11の実装に含まれているrgb.txtを使う。このファイルはWindowsには存在しないので適当なUnixマシンから持ってくる。私はUbuntu 10.04の/etc/X11/rgb.txtをコピーした。「rgb.txt」でググればこれみたいにネット上でも見つかるようだ。

ビルド方法

sngの本来のビルド方法ではconfigureを使うのだけど、手元ではうまく動作しない。そこでGCCを直叩きする。

それによる問題点として、ソースファイルにてインクルードしているconfig.hが見つからない。このファイルはconfigureの工程で生成されるファイルのようだ。今回は以下の内容のファイルで代用する。

/* config.h */
#define VERSION "1.0.5"
#define RGBTXT "D:\\usr\\share\\sng\\rgb.txt"

VERSIONはsngのバージョン番号で、RGBTXTはrgb.txtを配置する場所になる。RGBTXTは各環境ごとに都合のよい値にすること。

sngは開始時にrgb.txtを読み込む。RGBTXTで指定した場所にファイルが無いと初期化に失敗して処理を中断してしまう。本当は動的にrgb.txtの場所を指定できたら嬉しいのだけど、面倒なのでここでは放置しておく。

ソースファイルのうちmain.cは若干修正する必要がある。というのもWindowsの標準入出力はデフォルトではバイナリファイルをうまく扱えないので、CRTの_setmodeを使う必要があるからだ。またファイルを開く時も明示的にバイナリモードを指定する必要がある。

オリジナルのソースとの差分は以下の通り。

*** main.c	2010-11-06 02:29:14.000000000 +0900
--- main.c.mingw	2012-06-16 22:29:46.609375000 +0900
***************
*** 4,9 ****
--- 4,21 ----
  #include <stdlib.h>
  #include <ctype.h>
  #include <unistd.h>
+ 
+ #ifdef __MINGW32__
+ #include <fcntl.h>
+ #include <io.h>
+ #ifndef STDIN_FILENO
+ #define STDIN_FILENO 0
+ #endif
+ #ifndef STDOUT_FILENO
+ #define STDOUT_FILENO 1
+ #endif
+ #endif /* __MINGW32__ */
+ 
  #include "png.h"
  #include "sng.h"
  #include "config.h"
***************
*** 160,165 ****
--- 172,194 ----
      _wildcard(&argc, &argv);   /* Unix-like globbing for OS/2 and DOS */
  #endif
  
+ #ifdef __MINGW32__
+     {
+ 	static const int fd[] = { STDIN_FILENO, STDOUT_FILENO, };
+ 	size_t nfd;
+ 
+ 	for (nfd = 0; nfd < (sizeof(fd) / sizeof(fd[0])); nfd++)
+ 	{
+ 	    errno = 0;
+ 	    if (_setmode(fd[nfd], O_BINARY) == -1)
+ 	    {
+ 		perror("_setmode");
+ 		exit(1);
+ 	    }
+ 	}
+     }
+ #endif /* __MINGW32__ */
+ 
      while(argc > 1 && argv[1][0] == '-')
      {
  	switch(argv[1][i]) {
***************
*** 238,251 ****
  	    if (verbose)
  		printf("sng: converting %s to %s\n", argv[i], outfile);
  
! 	    if ((fpin = fopen(argv[i], "r")) == NULL)
  	    {
  		fprintf(stderr,
  			"sng: couldn't open %s for input (%d)\n",
  			argv[i], errno);
  		continue;
  	    }
! 	    if ((fpout = fopen(outfile, "w")) == NULL)
  	    {
  		fprintf(stderr,
  			"sng: couldn't open %s for output (%d)\n",
--- 267,280 ----
  	    if (verbose)
  		printf("sng: converting %s to %s\n", argv[i], outfile);
  
! 	    if ((fpin = fopen(argv[i], "rb")) == NULL)
  	    {
  		fprintf(stderr,
  			"sng: couldn't open %s for input (%d)\n",
  			argv[i], errno);
  		continue;
  	    }
! 	    if ((fpout = fopen(outfile, "wb")) == NULL)
  	    {
  		fprintf(stderr,
  			"sng: couldn't open %s for output (%d)\n",

__MINGW32__で#ifdefしている部分を_WIN32や_WIN64にすればVisual C++でもビルドできるかもしれない……と思ったけどunistd.hをインクルードしているのでダメかも。

ディレクトリsng-1.0.5直下にconfig.hをコピーし、main.cに上記のパッチを適用したら、次のようなコマンドでビルドする。

gcc -Wall -ansi -O2 -o sng main.c sngc.c sngd.c -I..\..\libpng\inc -L..\..\libpng\lib -L..\..\zlib\lib -lpng -lz

libpng-1.2.49のヘッダとライブラリ*3、zlibのライブラリを置いてあるディレクトリを指定した上で、オプション -l でlibpngとzlibをリンクするように指示すること。

これでsng.exeのみがビルドされる。config.hのRGBTXTで指定した位置にrgb.txtを置いた上でsngを呼び出してみる*4。同梱のtest.sngからPNGを生成してもよし、適当なPNGファイルをSNG化してもよし。

入出力をバイナリモードにした代償として、出力するSNGファイルは改行がLFとなる。入力するSNGファイルも改行をLFにしておいた方が無難だろう。私自身、CRLFのファイルでは試していない。

なお、この方法でビルドしたsngが本当に正しく動いてくれているか否かは不明だ。

*1:最初はPNGのチャンクを自前で解析してテキスト出力しようと考えた。次にlibpngで解析した方が楽かと思い直した。そして最後にsngのことを思い出した。

*2:『The Art of UNIX Programming』に基づく、私の勝手な印象。当然ながらESR本人との面識は全く無い。

*3:libpng-1.5.10ではビルドできなかった。1.5系では構造体の中身を隠してアクセサ関数を使用するように変更されていて、公開ヘッダからは構造体の中身が見えない。sngのソースでは構造体の中身に直接アクセスしている為、中身が隠されている1.5系のヘッダではコンパイルできない。

*4:DLLを使う場合はlibpng12.dllとzlib1.dllが必要なので注意。