GIFデコード処理を作る際に、注意しなければいけないポイント

2. 画像を展開した後、余ったデータを読み飛ばさなければいけない

前のページで説明したように、GIFデコーダのプログラムは、終了コードを読み込む前に、画像の展開終了を判断します。
画像が一枚だけ入ったGIFファイルならば、それで処理終了としてしまってOKなのですが、アニメーションGIFファイルの場合は問題があります。
一つ目の画像データの後に、次のアニメーションフレームの画像データが続いているので、処理を継続して、次の画像データをデコードする必要があるからです。

プログラムは、終了コードを読み込む前に展開終了を判断したので、まだ、一つ目の画像データの終了コードが読み込まれずに残っています。
そこで、次のアニメーションフレームの画像データを読み込み始める前に、一つ目の画像データの終了コードを読み飛ばさなければなりません。

終了コードを読み飛ばすためには、データブロックの残りのビットを捨ててしまえばよいのです。
前のページのGIFファイルを例に挙げると、プログラムは、6バイトのデータブロックのうち、青色で囲んだ範囲まで読み込んで、展開終了と判断します。


終了コードの4ビット分が、まだ読み込まれずに残っています。
プログラムは、残っている終了コードのことは気にせずに、6バイトのデータブロックの末尾まで、読み込みポインタを進めてしまえば良いのです。
変数pBlockBaseにデータブロックの先頭アドレス、変数BlockSizeにデータブロックのバイト数が入っているとすれば、

	     ・     
	     ・     
	     ・     
	一つ目の画像の展開終了

	pBlockBase += BlockSize;  // 終了コードを読み飛ばして、現在のデータブロックの末尾まで進める。
	pBlockBase += 1;      // 画像データ終了を表す「00」を読み飛ばす。

	二つ目の画像の展開開始
	     ・     
	     ・     
	     ・     
たいていは上述の方法でOKなのですが、稀に、2フレーム目の画像がデコードエラーになってしまうGIFファイルがありました。
たとえば、このようなGIFファイルです。
15×15ピクセル・2フレームのアニメーションGIFファイルで、1フレーム目は全ピクセルが違う色、2フレーム目は白一色で塗りつぶしてあります。

GIFファイルの内容の一部を、HEXエディタでダンプしてみました。
緑色の部分が、一つ目の画像データです。


画像データの部分だけを抜き出して、説明を加えてみました。
ちょっと長いですが・・・


上図の右下あたりに注目してください。
GIFのデータブロックは、各々が最大255バイトまでという制限があります。
画像全体のピクセルに相当するコードは、最初のデータブロックに収まっているのですが、終了コードだけが収まりきらず、次のデータブロックに分割されてしまっています。

プログラムは、最初のデータブロックの末尾にある、終了コードを読み込む前に、展開終了を判断します。
一つ目の画像データの終了コードを読み飛ばすために、最初のデータブロックの末尾まで読み込みポインタを進め、引き続き、次のアニメーションフレームのデコードを開始しようとします。
ところが実際には、読み込みポインタは、まだ、一つ目の画像データの、二つ目のデータブロックを指しています。
プログラムは、一つ目の画像データの終了コードの残り3ビットの部分を、次のアニメーションフレームの画像データと見なしてデコード処理を開始してしまって、デコードエラーになるのです。

エラーを避けるためには、一つ目の画像データの終了コードを読み飛ばす際、終了コードの一部のビットが、次のデータブロックにも分割されて格納されていないかを調べます。
もし、終了コードの一部のビットが、次のデータブロックにも分割されて格納されていたら、次のデータブロックも読み飛ばせば良いのです。


	     ・     
	     ・     
	     ・     
	一つ目の画像の展開終了

	pBlockBase += BlockSize;      // 終了コードを読み飛ばして、現在のデータブロックの末尾まで進める。
	BlockSize = *pBlockBase++;     // 画像データ終了を表す「00」、または、次のデータブロックのバイト数を読み込む。
	if(BlockSize != 0x00) {       // 終了コードの一部のビットが、次のデータブロックに分割されて格納されていたら・・・
	    pBlockBase += BlockSize;  // 次のデータブロックも読み飛ばす。
	    pBlockBase += 1;      // 画像データ終了を表す「00」を読み飛ばす。
	}

	二つ目の画像の展開開始
	     ・     
	     ・     
	     ・     

Sun Apr 12 20:48:20 JST 2009 Naoyuki Sawa (nsawa@piece-me.org)