GIF解析

 GMW-counter、見てくれたでしょうか??ついにアニメーションGIFを自分で作るまでになっちゃったよぉ〜〜。(^^;;)というわけで、それまでの経緯とGIF解析についてちょっと話したいと思います。

★イメージカウンタの変遷
 まあ、一年も前の話ですが、CGIがやりたくなって、とりあえず、カウンタなんかを作ってみたわけです。これはまあ、とてつもなくできの悪いしろものでして・・・なにが悪いか、ってHTMLでタグを出力するというものだったということですからね。(^^;
 どうにかしてIMGタグだけで起動できないものか・・・その方法を探してました。(←その時はほんとに初心者だったんだなぁ・・・。(遠い目))そこで出会ったのが「fly」。これを試行錯誤して・・・なんとか使えるようになって、イメージカウンタを作ってみたわけです。なかなか自由にできるんですよ、flyは。結合、GIF作成思いのまま。しかし、実際にやろうとすると問題があったのです。それは、fly.exeがアップロードできなかったのです。(^^;)サーバーの設定のせいなのか、exeという拡張子がいけなかったのかわかりませんが、とにかくできなかったわけです。でも・・・それでよかったんでしょうね・・・できちゃったらここまでこなかったかもしれませんし・・・。
 さて、困った、どうしよう・・・。といろいろカウンタを探してみたわけです・・・で、CGIのIMGのみでできるカウンタの一つを解析してみたわけです。で、出会ったのがgifcat.plというGIF画像連結ライブラリでした。

★GIF解析への第1歩
 gifcat.plを作ったとほほさんのページ。gifcat.plとともにGIF解析のページがあったのです。それを見たわたしはGIF解析してみようかなぁ、という気になったわけです。しかし、そうすぐにはできません。いろいろやりたいこともあったりしたし・・・。で、実際にGIF解析に取りかかったのは夏季休講の時でしょうか。やっとまとまった暇ができたわたしは適当なGIFファイルをバイナリエディタで開き、そのページを見ながら解析していったわけです。
 ・・・なるほど、ほんとにその値になっている・・・とか確かめながら少しずつ理解していったのです。
 そして、理解完了したのがだいたい8月頃☆そのページに書いてあることは9割がた理解しました♪そして、早速gifcat.plを改造。(爆)縦方向連結してみたり、煩雑なアニメーションGIFの出力をしてみたりしました。(^^;

★もっと深いGIF解析
 それからしばらくはそれ以上の深入りはしなかったし、しようとも思っていませんでした。とりあえず、GIFファイルの概要・・・アウトラインはほぼ完全に理解したつもりでいたし、それ以上のことはカウンタにはいらないだろうと思っていたのです。それともう一つ大きな理由・・・それはいろいろ言われているGIFの特許問題。ImageDataにはLZW法という圧縮アルゴリズムが使われていて、それには特許がかけられている、と知っていたから・・・そこの解析は特許に触れることになる→分かったところで公開もできない、という考えや、特許がかけられている→使用料を払わないとLZW法エンコード方法、デコード方法は分からない、という推測があったというのが大きな理由でした。
 ということでカウンタには触れずそのままずっと・・・。ところが、そこに一つの転機が訪れました。PerlのみでGIFを出力しているCGIを見つけてしまったのです。さぁ、これで解析熱が再加熱してきました。(爆)
 特許に触れないでGIFを作れる、というのはあることで知っていました。そんなツールがあったので。画像データの圧縮にLZW法を使わなければ特許は関係ない、ということらしい、ということも・・・。
 そーゆーわけで、そのCGIを解析。(死)・・・しかし、これを真似るだけではよくない、というか、どーしてそうなるのか、を理解しないとダメだ!と思ったわたしはネットでいろいろ探したり、聞いたりしてみる。で、ついにここまで来た・・・GIFの仕様書(和訳版)。(爆)英語の仕様書は持ってました、でもプリインストールされていた翻訳ソフトに通したところで意味のわかる訳にはならずどーしたものかと思っていたのです。
 でも、それでも分からないことが・・・それが画像データの圧縮方法。RLE法、という方法があるというところまでは来ました・・・しかし、それがどういう圧縮方法か、というところは分かっても、どのようにGIFに使うか、ということが分からなかったのです。
 さて、また検索です・・・検索エンジンは便利ですね。(笑)そして、ついに(20分くらいですけど。(爆))発見しました。LZWを使わずにGIFファイルを作ろう!というのがありました!これを理解してやっとRLEというものを理解しました♪
 最後にデータ構造です。圧縮方法がわかっても、それ以前にデータがどのように作られるか、が分かってないとどーにも。(^^;)CGIを眺めて、仕様書を眺めて、実際に確かめて・・・8色GIFの方は機械的に分かったんですが、どうしてそうなるのか、を分からないと・・・16色GIFの出力がさっぱりできなかったんです。考えて考えて・・・やっとある仮説にあたりました。で、それを試してみる・・・・・・と・・・をををっ!!ホントにできたぁぁ!!(笑)RLE法もこれに適用してみると・・・をををををっ!!できたぁぁ!!(爆笑)と、ゆーわけで、このカウンタと相成ったわけです。

★ImageData構造について
 ここからは難しいことを書きます。(^^;)と、言ってもわたしが理解したように書きますので読みたい人は読んでください・・・けっこう分かる・・・かもしれません。とりあえず、概要はさっきのところで十分だと思うので書きません。(爆)わたしなりのImageData構造の理解を書きますので、仕様書と一緒に読めばいいかと。

・ImageDataの構造はその直前にあるLZW Minimum Code Sizeの値で決定します。その値が3(基本的に8色)ならば画像1ドットを表すのに4bitを使い、4(基本的に16色)ならば画像1ドットを表すのに5bit使います。

・結局ImageDataはColorTableのIndexの羅列です。4bitなら楽ですね。


2     |     1
4     |     3
6     |     5



 という順番でIndexが読みこまれていくわけです。(それぞれのセルが1Byteを表します。)
 例えば・・・0x12 0x34 0x56・・・というところなら・・・2,1,4,3,6,5・・・と読みこまれそのIndexに対応したColorTableにある色が1ドットずつ表示されていくわけです。
 5bitになるとちょっと複雑ですね。以下のようにIndexが梱包されます


2  2  2 | 1  1  1  1  1
4 | 3  3  3  3  3 | 2  2
5  5  5  5 | 4  4  4  4
7  7 | 6  6  6  6  6 | 5
8  8  8  8  8 | 7  7  7


 画像1ドット表すのに5bitだからこうなるのは自然なんですよね。それぞれのセルが1Byte、それぞれの数字は1bitです。
 だから1,2...8と8つを梱包すると0x41 0x0C 0x52 0xCC 0x41となります。
 1,2..8を5bit,2進数で表すと
[01000][00111][00110][00101][00100][00011][00010][00001]
 1Bytesずつ梱包すると
(01000001)(11001100)(01010010)(00001100)(01000001)
 右から0x41 0x0C 0x52 0xCC 0x41となるでしょ。(←0xは16進数である、ということを表します。)

・というのが基本ですが、無駄があるわけです。8色ならIndexは0〜7しかないはずなのに画像1ドットに表すbit数は4・・・つまり0〜Fです。8〜Fはなんなのか、という問題になるわけです。
 8というのはクリア符号、9は終了符号と言うことは仕様書にもあるとおり。終了符号というのはそのまま、これで符号の配列は終わりだ、と表しているわけです。でA以降はというと、圧縮の際に使われる符号、と考えていいと思います。
 さて、クリア符号の意味ですが・・・わたしなりの理解で言います。(実際は違うかもしれません。(^^;))ImageDataの1番最初、そこで使用できる符号は0〜8のみなのです。で、その次に使うことができる符号は0〜9、3文字目では0〜A・・・となるのです。とすると9文字目には0〜10(←16進数)まで使用することができてしまう。そうするとそこからは画像1ドットを表すのに5bit使用することになってしまいます。そこでクリア符号です。クリア符号を出すとその時点で0〜8の状態に戻ります。クリア符号で1ドットを表すのに4bit、という状態を維持している・・・とわたしは理解してます。というわけで、この場合8文字目にはクリア符号を出力しておかなければおかしくなる・・・んじゃないかと。(爆)

・16色もほぼ同様。4bitを5bitにしてそのまま自然に上の内容を変えるだけ、だと思います。(ぉ

・最後にRLE(RunLengthEncode)という圧縮方法について解説。
 圧縮方法自体は単純です。同じIndexが連続しているのをそのIndexとそれが続く回数という風にして圧縮してしまおう、というだけです。
 で、さっき言ったことを思い出しましょう。8色の場合、圧縮に使える符号はA以降です。それでやってみましょう。
0→0(RLE使うまでもなし)
00→00(これも同様)
000→0A(圧縮できました。)
 ここでさっきのことをもう一つ思い出す。1文字目に使えるのは0〜8・・・ということです。わかりやすいようにクリア符号と一緒に出力してみましょう。
000→80A
0000→80A0(←3文字目に使えるのはAまでなので次にいきます。別にA以降で表さなくても0を出力してしまえば0000と同じになるからこう。)
00000→80AA(1文字圧縮)
000000→80AB(2文字圧縮)
0000000→80AB0


000000000000000000000→80ABCDE(14文字圧縮:これ以上やると使用できるIndexが5bitになってしまうのでここまで。次はクリア符号を出力するべき)
 16色、5bitだと最終的にはもっと圧縮できます。
000→100012(注・左は1つずつ、右は2つで一つと考えてください。つまり0x00 0x00 0x00→0x10 0x00 0x12を表しているということで。ちなみに5bitでは0x10がクリア符号。)
0000→10001200
00000→10001212


0×105→100012131415161718191A1B1C1D1E(90文字圧縮:これ以上やると6bitになっちゃう。)
まで行きます。ここまで同じ文字が続けばかなり圧縮できるというわけです。

 以上。
 読んでくれた方、ありがとうございます〜〜。