もぐてっく

人は1つ歳をとるたび、1ビットづつ大きくなれると信じてた。

ぅゎzlibっょぃ

ちょっと前に作ったrootウインドウのキャプチャをpng形式で吐くプログラム。
その出力画像がたまに崩れる問題を調査した。


こんな感じで1行毎にzlibでdeflate圧縮したデータをidatチャンクに放り込むプログラムだったわけです。
(idatの数をなるべく少なくした方が圧縮率は良くなると思いますが、要求仕様よりも十分小さいので良しとしてます。)

char out_data[1行分 * 4];
char line[1行分];
int line_bytes;

for(int i = 0;i < height;i++)
{
	/* 1行分のビットマップを作る処理 */
	create_line(line,&line_bytes);

	z.next_in = line;
	z.avail_in = line_bytes;

	if(i != height - 1)
	{
		flag = Z_NO_FLUSH;
	}
	else
	{
		flag = Z_FINISH;
	}

	do
	{
		z.next_out = out_data;
		z.avail_out = sizeof(out_data);

		ret = deflate(&z,flag);

		if(ret != Z_OK && ret != Z_STREAM_END)
		{
			/* エラー終了 */
		}

		/* idatチャンクに1行分の圧縮データを格納 */
		write_idat_chunk(out_data,sizeof(out_data) - z.avail_out);
	}
	while(z.avail_in > 0);	
}

これを実行するとサイズ0のidatチャンクが大量に出来た挙句、たまにout_dataを使い尽くす程のデータを吐いてる。idatの数が限度を超えてるのか、はたまた1つのidatに格納出来るサイズを超えるのかは不明だけど、この妙な動きが影響してるのは間違いない。


printfデバッグしてみるとdeflate()呼び出し後にz.avail_inが0になってるのにz.avail_outが減ってないことが判明。これだ・・・。

どうもdeflate関数の中である程度データをバッファしてるみたい。入力が多い方が冗長なパターンが増えて圧縮率が高まるんでしょうな。なるほどと。
しっかしこれだとフラッシュされるタイミングが制御不能だから、処理が煩雑になるなぁ・・・毎回フラッシュしてくれればいいんだけど・・・FLUSH?あっ!


と、言うわけでフラグのZ_NO_FLUSHをZ_SYNC_FLUSHに変えてみたらバッチリ毎回フラッシュしてくれるようになりました。


サンプルが載ってるサイトは軒並み「Z_NO_FLUSHを使え」としか書いてなかったので、他にオプションがあるなんて思ってなかったぜ!と言うお話。