イントラネットのページにアクセスカウンタをつけてみる。 フリーのものを持ってきてもいいんだけど、 ふと思い立って C で作ってみることにした。
とくにこる必要はないので、出力はモノクロでよしとして X Bitmap 形式を使うことにした。 以下備忘。
X Bitmap 形式は C 言語コードの単なるテキストデータである。
#define
で画像ファイルの幅と高さを指定し、
ピクセルデータをバイト配列で指定する。
#define ex_width 56 #define ex_height 16 static char ex_bits[] = { 0x00, 0x00, ... };
数字のフォントはあらかじめデータとして 8 × 8 のビットマップデータを作っておいて、 横につなげて出力するようにした。
で、この CGI をテスト用のマシンにインストールしたンだけれど、 なぜか別のマシンから NN 6.2 でアクセスすると表示されない。 ローカルから IE 5.5 でアクセスすると表示される。
NN で X Bitmap が表示されないので、 GIF で出力することにする。 ただしライセンスの問題があるので、 無圧縮の GIF で出力する必要がある(参考: GIF Info: LZW 圧縮伸長法の特許について)
そこで無圧縮 GIF 出力ライブラリ libungif, a library for using uncompressed GIF をゲット。
しかしなんか使い方がよくわからず。 サンプルというかライブラリのほかにユーティリティプログラムがたくさんあるので それを参考に試行錯誤するもうまくいかず。 どうしたことか。
基本的には以下のようにすればよいはず。
gf = EGifOpenFileHandle (fileno(stdout))
などとして
GIF イメージを出力するファイル記述子を指定 。
ここでは CGI なので標準出力を使用。co = MakeMapObject (2, NULL)
でカラーマップオブジェクトを作成。
モノクロなので色数 2 を指定。Colors[0-1].{Red,Green,Blue}
メンバを初期化。EGifPutScreenDesc (gf, w, h, 1, 0, co)
として論理画面記述部を設定。EGifPutImageDesc (gf, 0, 0, w, w, 0, NULL)
として画像記述部を設定。EGifPutLine (gf, img, w)
で 1 行ずつイメージを出力。
やっぱ、これからは PNG だぜってことで、libungif
はあっさり放擲。
libpng
を使うことにした。
png_ptr = png_create_write_struct (PNG_LIBPNG_VER_STRING, 0, 0, 0)
として png_struct
を作成。info_ptr = png_create_info_struct (png_ptr)
としてpng_info_struct
を作成。png_init_io (png_ptr, stdout)
で出力先を初期化。png_set_IHDR (png_ptr, info_ptr, w, h, 1, PNG_COLOR_TYPE_GRAY, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT)
としてヘッダを設定。png_write_info (png_ptr, info_ptr)
として png_info_struct
を出力png_write_row (png_ptr, img)
で1行ずつ出力。png_write_end (png_ptr, info_ptr)
で出力を完了。png_destroy_write_struct (&png_ptr, &info_ptr)
で各構造体を破棄てな流れである。 出力する画像は、戯れに GDI API で作成することに。
DrawText (dc, text, -1, &rect, DT_LEFT | DT_CALCRECT)
で出力する画像のサイズを計算。CreateBitmap (1, 1, w, h, NULL)
でビットマップオブジェクトを作成。DrawText (dc, text, -1, &rect, DT_LEFT)
で実際に描画。GetDIBits (dc, bmp, 0, h, 0, &bmp_info, DIB_RGB_COLORS)
でビットマップ情報を取得。bmp_info.biHeight *= -1
として値をマイナスにする。
正の値だと上下さかさまの画像になってしまう。
より汎用的なコードにするには -abs (bmp_info.biHeight)
とかして絶対値をとらないと元の値が負の場合に期待した結果にならない。img = malloc (bmp_info.biSizeImage)
として画像用のメモリを確保。GetDIBits (dc, bmp, 0, h, img, &bmp_info, DIB_RGB_COLORS)
でビットマップデータを取得。
で、Mingw でコンパイルしなおしたら、セグメンテーション・フォルトで落ちる。
当たり前といえば当たり前であるな。
しょーがないので libpng
を -mno-cygwin
オプションでコンパイルしなおすことにする。
Cygwin の Setup でソースファイルをダウンロード。
Patch とビルド用のスクリプトもいっしょにダウンロードされるので、
./libpng-version.sh prep
とかしてメイクの準備。
Makefile を書き換えて Mingw でコンパイルするようにする。
インストール先もオリジナルとは変えておく。
CC=gcc -mno-cygwin prefix=/usr/local/mingw
zlib も Mingw 版を作らないとだめなようなので zlib もとってくる。
./zlib-version.sh prep
、
CC='gcc -mno-cygwin' configure --prefix=/usr/local/mingw
、
make
とかした。
libpng は Mingw でリンクしても cygpng2.dll
が必要になる。
gcc に -static
を指定してリンクすること。
で、この CGI をテスト用のマシンにインストールしたンだけれど、 なぜか別のマシンから NN 6.2 でアクセスすると表示されない。 ローカルから IE 5.5 でアクセスすると表示される。 X Bitmap と同じじゃねーか。
やっぱ画像の標準はまだまだ GIF でしょうってことで、 GIF の仕様書を元に自分でコードを書くことにした。 無圧縮 GIF の作り方は Graphics File Formats FAQ part1: Is there an uncompressed GIF format を参照。 以下超訳。
GIF の仕様にはビットマップデータを無圧縮で保存する方法は定義されていない。 すべてのビットマップデータは LZW アルゴリズムで圧縮して保存するようになっている。 圧縮しないでそのままビットマップデータを保存しても、 それを表示できるプログラムはない。
それでは GIF ファイル中の圧縮されたデータを、 LZW 特許で説明されているフォーマットにならないように変更する、 しかも GIF 復号器で読めるような、方法はあるのか。 答えは Yes である。
GIF ファイルが圧縮される場合、 初期 LZW 符号テーブルが画像の色深度に基づいて作成される。 たとえば 4 ビット/ピクセルのビットマップは、 18 項目の LZW 符号テーブルで符号化される; 00000b から 011111b の範囲の 16 個の色インデックスと クリア符号 (10000b) とデータ終了コード (10001b) である。
LZW 符号化のプロセスにおいては、 データからの色コードは新しいテーブルの項目として使用され、 その新しい項目の構成は LZW 符号化の心臓部である。 もし、符号器が初期テーブルだけ使用したとすれば、 新しいテーブル項目の符号は作成しない。 したがってすべての符号化されたデータは、 GIF ファイルのカラーテーブルに保存された色インデックスを表現するコードになる。
このプロセスは、Dr. Tom Lane によって 1996 年 12 月 5 に comp.graphics.misc への投稿の中で説明された:
... そのアイデアはシングル-シンボル・ストリングコードだけを出力するというもの、 加えてデコーダが符号の幅を増幅しないようにするために、 クリア符号を時々出力するようにするものである。 このモードでは符号器は単純に N ビットピクセルの値を N+1 ビットフィールドにパッキングしてカウントを維持します; これは特許にはない。 注意しなければならないのは、 データはただ単に圧縮されないだけでなく、*膨張* することである; 8 ビット GIF であれば 1 ピクセルあたり 9 ビット必要になる。 色深度の低いデータに対してこのトリックを使うのはお勧めできない。 最悪のケースは 1 ビット (モノクロ) のデータである; 2 ビット/ピクセル必要になるだけでなく、 すべてのシンボルは符号の幅を 2 ビットに維持するためにクリアしなけければならない。 ... 正味の結果は 4 倍に膨張する。
この符号器は最終的にデータのすべての N ビットを N+1 ビットにし、 クリア符号を 2N - 2 符号ごとに追加して保存するので、 8 ビットの「無圧縮」GIF 画像は LZW 圧縮して保存されたGIFよりも 1/8 大きくなる。
Tom は後日これを説明した。
注意、しかしながら、 デコーダにシンボルの幅を大きくさせないために十分クリア符号を挿入しなければならない、 あるいは現在のシンボルの幅がそうであるべきであることを記録しなければならない。 少ししか細部を見ていないが、思うに
2N - 2
毎にクリアしなければならない、 ここで N はデータの深度による、 もしシンボルの幅を N+1 ビットに押さえようとするのであれば。
というわけで、この説明と GIF の仕様書から画像データの書き出しは以下の手順となるはず。
ここで作成する画像データの色深度を 1 から 8 に変更しなければならない。
注意すべきは、CreateBitmap
で作成できるビットマップオブジェクトは、
色深度 1 か出力デバイスと同じ色深度のビットマップだけである点。
はまりました。
仕方がないので CreateCompatibleBitmap
で作成するように変更。
なんとなく CreateDIBSection
をつかって
デバイス非依存ビットマップを作成したほうがいいような。
でもコードをたくさん変更する気力がなくなってきたのでいいや。
これが eXtreme programing か。なのか。
またGetDIBits
の最初の呼び出しで情報を取るときに、
取り出したい色深度を指定するとエラーになるようす。
ビットマップデータを実際に取り出すときに指定しないといけない。
なお、ちょっと前にはまったように、
ビットマップデータの行は 32 ビットアラインメントに配置されるので、
受け取るメモリはその分余計に確保しておかねばの娘だ。
しかしなんでかごみが表示されるのみ。
そういえば GIF ファイルに 2 バイト整数データを書き込むときは
リトルエンディアンであったことを思い出した。
仕様書には複数バイトの整数欄は、最下位バイトが最初に並べてある
とある。
昨日のプログラムでは 9 ビットの符号を書き込むときに、
そのままパックして出力していた。
たとえば 「1 0001 1001
」に続けて「1 1001 0001
」を出力しようとしたとき、
「1 0001 1001 1 1001 0001
」と書き込んでいた。
9 ビットでも結局最小単位はバイトなので 2 バイトに拡張されるので
「1 0001 1001
」は「0000 00001 0001 1001
」となる。
そしてリトルエンディアンであるとすれば、
下位バイトを先に出力しなければならないので
「0001 1001 0000 0001
」と出力しなければならない。
で 「1 0001 1001
」と「1 1001 0001
」の各メモリイメージは
「0001 1001 0000 0001
」と「1001 0001 0000 0001
」であり、
これを詰め込むと 2 バイトに拡張した先頭のビットを削り取って、
「0001 1001 0010 0011 0000 0011
」となる。
なので昨日の手順は以下のように変更。
unsigned short
の配列に入れる。unsigend short *cp, *code; unsigend char *pp, *packed; int flood = 0; ... for (cp = code, pp = packed; cp < code + size; cp ++, pp ++) { *(unsigend short*)pp |= (*cp << flood); if ((flood = (flood + 9 - 8) % 8) == 0) pp ++; } if (flood > 0) pp ++; packed_size = pp - packed;
で、この CGI をテスト用のマシンにインストールしたンだけれど、 なぜか別のマシンから NN 6.2 でアクセスすると表示されない。 ローカルから IE 5.5 でアクセスすると表示される。 X Bitmap と PNG と同じじゃねーか。
ふと戯れに別マシンから telnet で http ポートにアクセスしてみた。 HTTP エラー 401 がかえってきた。 単にアクセス権の問題じゃねーか。 なんで匿名アクセスが不許可になってンだっつーの。 この一週間は何だったんだ。 つーか、デバッグの基本を忘れた罰ってこったな。
またネットで遊んでいるうちに時間がなくなって床屋にいけず。 Amazon などで本を数冊注文。 それと今年の手帳をまだ購入していなかったのでそれも発注。 この手帳発行部数が少ないせいかこのあたりではなかなか見かけない。 そのうえこのところ本屋へ赴くということがめっきり少なくなったため、 さらに遭遇する機会が少なくなっていたというわけ。 しかしまあ今のところ手帳がなければ困るといった事態にならないのもさみしいもの。 忙しい時分はスケジュール帳が真っ黒になるほど埋まっていたものだが。
現代舞踊鑑賞。みなアマチュアなのによく体が利くものだと感激。 とはいえ肉体としてはまだまだ研ぎ澄まされきれていないのがほほえましいか。 こういうのみると精神が向上してくるね。 今週はずっとプチ鬱状態だったので何とか持ち直したかんじ。
ネットで見かけた「詞苑間歩(上)」を図書館で借りてくる。 旧漢字旧仮名遣いでかかれていて読んでいて楽しい。 久々に脳細胞が活性化されていくかんじを味わう。