depressionist in depressive mood

[prev|next|index]2002:09

[prev|next]20020225Mo

イントラネットのページにアクセスカウンタをつけてみる。 フリーのものを持ってきてもいいんだけど、 ふと思い立って 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 でアクセスすると表示される。

[prev|next]20020226Tu

昨日の続き

NN で X Bitmap が表示されないので、 GIF で出力することにする。 ただしライセンスの問題があるので、 無圧縮の GIF で出力する必要がある(参考: GIF Info: LZW 圧縮伸長法の特許について)

そこで無圧縮 GIF 出力ライブラリ libungif, a library for using uncompressed GIF をゲット。

しかしなんか使い方がよくわからず。 サンプルというかライブラリのほかにユーティリティプログラムがたくさんあるので それを参考に試行錯誤するもうまくいかず。 どうしたことか。

基本的には以下のようにすればよいはず。

  1. gf = EGifOpenFileHandle (fileno(stdout)) などとして GIF イメージを出力するファイル記述子を指定 。 ここでは CGI なので標準出力を使用。
  2. co = MakeMapObject (2, NULL)でカラーマップオブジェクトを作成。 モノクロなので色数 2 を指定。
  3. 作成したカラーマップオブジェクトの Colors[0-1].{Red,Green,Blue}メンバを初期化。
  4. EGifPutScreenDesc (gf, w, h, 1, 0, co) として論理画面記述部を設定。
  5. EGifPutImageDesc (gf, 0, 0, w, w, 0, NULL) として画像記述部を設定。
  6. EGifPutLine (gf, img, w) で 1 行ずつイメージを出力。

[prev|next]20020227We

昨日の続き

やっぱ、これからは PNG だぜってことで、libungif はあっさり放擲。 libpng を使うことにした。

  1. png_ptr = png_create_write_struct (PNG_LIBPNG_VER_STRING, 0, 0, 0) として png_struct を作成。
  2. info_ptr = png_create_info_struct (png_ptr) としてpng_info_struct を作成。
  3. png_init_io (png_ptr, stdout) で出力先を初期化。
  4. 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) としてヘッダを設定。
  5. png_write_info (png_ptr, info_ptr) として png_info_struct を出力
  6. png_write_row (png_ptr, img) で1行ずつ出力。
  7. png_write_end (png_ptr, info_ptr) で出力を完了。
  8. png_destroy_write_struct (&png_ptr, &info_ptr)で各構造体を破棄

てな流れである。 出力する画像は、戯れに GDI API で作成することに。

  1. DrawText (dc, text, -1, &rect, DT_LEFT | DT_CALCRECT) で出力する画像のサイズを計算。
  2. CreateBitmap (1, 1, w, h, NULL) でビットマップオブジェクトを作成。
  3. DrawText (dc, text, -1, &rect, DT_LEFT)で実際に描画。
  4. GetDIBits (dc, bmp, 0, h, 0, &bmp_info, DIB_RGB_COLORS) でビットマップ情報を取得。
  5. bmp_info.biHeight *= -1 として値をマイナスにする。 正の値だと上下さかさまの画像になってしまう。 より汎用的なコードにするには -abs (bmp_info.biHeight) とかして絶対値をとらないと元の値が負の場合に期待した結果にならない。
  6. img = malloc (bmp_info.biSizeImage) として画像用のメモリを確保。
  7. 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 prepCC='gcc -mno-cygwin' configure --prefix=/usr/local/mingwmake とかした。

libpng は Mingw でリンクしても cygpng2.dll が必要になる。 gcc に -static を指定してリンクすること。

で、この CGI をテスト用のマシンにインストールしたンだけれど、 なぜか別のマシンから NN 6.2 でアクセスすると表示されない。 ローカルから IE 5.5 でアクセスすると表示される。 X Bitmap と同じじゃねーか。

[prev|next]20020228Th

昨日の続き

やっぱ画像の標準はまだまだ 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. LZW 符号寸法出力。ここでは、色深度 8 にすることにしたので 8 を書き出す。
  2. すべてのピクセルを、先頭に 0 を追加した 9 ビットの符号に変換。
  3. 28 - 2 = 254 符号毎にクリア符号(2符号寸法 = 256) を挿入。
  4. 終端に情報終了符号 (クリア符号 + 1 = 257) を追加。
  5. データを最大 255 バイトのブロックに分割して出力する。 各ブロックの先頭にはブロック寸法を 1 バイト幅で出力する。
  6. すべて出力したらブロック終了符(0)を出力。

ここで作成する画像データの色深度を 1 から 8 に変更しなければならない。 注意すべきは、CreateBitmap で作成できるビットマップオブジェクトは、 色深度 1 か出力デバイスと同じ色深度のビットマップだけである点。 はまりました。 仕方がないので CreateCompatibleBitmap で作成するように変更。 なんとなく CreateDIBSection をつかって デバイス非依存ビットマップを作成したほうがいいような。 でもコードをたくさん変更する気力がなくなってきたのでいいや。 これが eXtreme programing か。なのか。

またGetDIBits の最初の呼び出しで情報を取るときに、 取り出したい色深度を指定するとエラーになるようす。 ビットマップデータを実際に取り出すときに指定しないといけない。 なお、ちょっと前にはまったように、 ビットマップデータの行は 32 ビットアラインメントに配置されるので、 受け取るメモリはその分余計に確保しておかねばの娘だ。

しかしなんでかごみが表示されるのみ。

[prev|next]20020301Fr

昨日の続き

そういえば 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」となる。

なので昨日の手順は以下のように変更。

  1. LZW 符号寸法出力。ここでは、色深度 8 にすることにしたので 8 を書き出す。
  2. すべてのピクセルを、先頭に 0 を追加した 2 バイトの符号に変換。 unsigned short の配列に入れる。
  3. 28 - 2 = 254 符号毎にクリア符号(2符号寸法 = 256) を挿入。
  4. 終端に情報終了符号 (クリア符号 + 1 = 257) を追加。
  5. 符号データを 9 ビットにパックしてバイト配列に入れる。 以下はリトルエンディアンな CPU でのみ正常に動作する汚いコード。
    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;
    
  6. データを最大 255 バイトのブロックに分割して出力する。 各ブロックの先頭にはブロック寸法を 1 バイト幅で出力する。
  7. すべて出力したらブロック終了符(0)を出力。

で、この CGI をテスト用のマシンにインストールしたンだけれど、 なぜか別のマシンから NN 6.2 でアクセスすると表示されない。 ローカルから IE 5.5 でアクセスすると表示される。 X Bitmap と PNG と同じじゃねーか。

ふと戯れに別マシンから telnet で http ポートにアクセスしてみた。 HTTP エラー 401 がかえってきた。 単にアクセス権の問題じゃねーか。 なんで匿名アクセスが不許可になってンだっつーの。 この一週間は何だったんだ。 つーか、デバッグの基本を忘れた罰ってこったな。

[prev|next]20020302Sa

またネットで遊んでいるうちに時間がなくなって床屋にいけず。 Amazon などで本を数冊注文。 それと今年の手帳をまだ購入していなかったのでそれも発注。 この手帳発行部数が少ないせいかこのあたりではなかなか見かけない。 そのうえこのところ本屋へ赴くということがめっきり少なくなったため、 さらに遭遇する機会が少なくなっていたというわけ。 しかしまあ今のところ手帳がなければ困るといった事態にならないのもさみしいもの。 忙しい時分はスケジュール帳が真っ黒になるほど埋まっていたものだが。

現代舞踊鑑賞。みなアマチュアなのによく体が利くものだと感激。 とはいえ肉体としてはまだまだ研ぎ澄まされきれていないのがほほえましいか。 こういうのみると精神が向上してくるね。 今週はずっとプチ鬱状態だったので何とか持ち直したかんじ。

[prev|next]20020303Su

ネットで見かけた「詞苑間歩(上)」を図書館で借りてくる。 旧漢字旧仮名遣いでかかれていて読んでいて楽しい。 久々に脳細胞が活性化されていくかんじを味わう。