All Articles

PHPでもアニメーションGIFを扱いたかったのでライブラリ書いたよ

またまた勉強成果の報告的なものです。

PHPでAnimated GIFをどう扱うか

ご存知の通り、PHPにバンドルされるGDライブラリでは、アニメーションGIFが扱えません。 じゃあ何とかしないとなーということで、そろそろバイナリに対して拒否反応を示さないように、ここらでしっかり触れておくことにしました。「ImageMagickを使えばいいじゃん」と言ってしまえばそれまでなんですが…。

できあがったものがこちらです

以下のgithubをお試しください。ソースコード中にも仕様についていくつか書いてあるので、そっちの方が早いかもしれません。

ysugimoto/GifManipulator

GIFの仕様とハマった所についても少し触れておきます。仕様書は以下のリンクにあります。

GIF89a Specification

GIFのフォーマットについて

GIFイメージファイルは、

開始部(Header)
論理画面記述部(Logica Screen Descriptor)
広域配色表(Global Color Table)
[ネットスケープ拡張(Netscape Extension)]
グラフィック制御拡張(Graphic Control Extension)
画像記述部(Image Descriptor)
終了部(Trailer)

というフォーマットで構成されています(各セクションの詳細は仕様をご覧ください)。大まかには以下のような情報がそれぞれ取得できます。

開始部(Header)

GIFファイル開始宣言。「GIF89a」という文字列固定(昔はGIF87aもあったそうですが、今は89aが一般的だそうです)。

論理画面記述部(Logical Screen Descriptor)

GIFキャンバスについての情報。縦/横のサイズや透過指標、広域配色表の有無、カラーパレットの数など、レンダリングに必要な情報はだいたいここにある。多分getimagesize()関数はここの値を使うんじゃないかな。

広域配色表(Global Color Table)

論理画面記述部にて、広域配色表を使うフラグがある場合、ここにカラーパレットが展開される。MAX255色。フラグがない場合、このセクションは現れない。

ネットスケープ拡張(Netscape Extension)

アニメーションGIFの場合、ここにアニメーションの指定がなされる。「\x21\xFF\xB」に続いて「NETSCAPE2.0」という文字列が含まれる。

グラフィック制御拡張(Graphic Control Extension)

続く画像に関する情報を保持するセクション。画像サイズ、画像配置の位置、狭域配色表の有無などはこのセクションに含まれる。ここに注意点がいっぱい

画像記述部(Image Descriptor)

3Byte(\x21\xF9\x04)の識別子に続いて、画像本体部分をLZW圧縮されたものが入る。現在はLZWデコーダを作ってないので、ぶっちゃけブラックボックスだけど、まぁサイズだけちゃんと保持できればそれほど問題にならないと思う。

終了部(Trailer)

イメージ終了宣言。\x3B固定値。

基本的に、上述の順にセクションが現れて、ブロックサイズなどで何Byteあるかを取得しながら順に解析していくことが可能です。画像が複数ある場合は、グラフィック制御拡張と画像記述部が繰り返し現れたりします(\x3Bが現れず、そのまま連結される仕様のようです)。また、論理画面記述部にて広域配色表を使わない場合、グラフィック制御拡張の広域配色表に画像ごとの狭域配色表が指定されている必要があるようです(何言ってるが分かりづいらいですが、解析を進める上での仕様を把握すると、はは〜ってなると思うので、仕様書をぜひ読んでみてください)

注意点など

ざざーっと上から解析していけばOKなのですが、アニメーションGIFを生成するツールに依って、若干規格と違うものが生成されることがあるようです。フォーマットとしてはInvalidなようですが、OSのプレビューアプリやブラウザはそれでも正しくレンダリングするので、フォーマットは厳格、というわけではなさそうです(逆に困るんですけど)。GDに通すとフォーマットエラーになります。今回、ぶちあたった問題はだいたい以下のようなものでした:

Netscape Extensionが変な位置に挿入されたGIF

もうこれはおかしいとしかいいようが無いんですが、上述の通り、ネットスケープ拡張は、広域配色表とグラフィック制御拡張の間に来るべきものです。以下に「The Netscape block must appear immediately after the global color table of the logical screen descriptor.」とあります。

GIF Application Extension: NETSCAPE2.0

でも、Webとかで生成できるツールで作ると、このセクションがグラフィック制御拡張と画像記述部の間にあったりするんですよね。ちゃん仕様通り生成しろよと言いたいんですが、ブラウザが表示してしまう以上は強く言えません…まぁ仕様云々を抜きにしたツールなんでしょうね。今回作ったライブラリはこれの対策のため、ネットスケープ拡張がどこで現れても解析されるようにちょっと工夫しています。ただし、一回限りの出現に限りますが。

論理画面記述部に対してグラフィック制御拡張部分のサイズが大きい

つまりは、「表示可能なキャンバスサイズに対して、配置する画像サイズが大きいとはみ出るのでレンダリングできないよ」とGDは言うようです。これは複数画像をリサイズする際に主に発生するため、一枚画像では発生しない問題のように思います。対策は、各画像のグラフィック制御拡張の画像サイズ部分のビットを論理画面記述部のサイズビットに合わせてFitさせることでOKでした。

まぁそんなわけで、「現在Webサイトで表示されているGIFなんてまぁ適当なフォーマットでブラウザがよしなにやってくれてるものが多い」ということが分かりました。ブラウザ偉い。

感想とかTODOとか

・バイナリを読める形式にダンプしながら解析しているので、実行速度は遅め。データ上不要な部分はバイナリのまま扱って、bin2hex()とか、pack()/unpack()のコール回数をもっと減らせそう。

・セクションパラメータがわかりやすいように、プロパティ名もフルネームでつけてるので長い。でもわかりやすいのでいいかな。

・LZW圧縮について調査して、エンコーダ/デコーダも作ってみたい。

・目がHexになった。

@haxeさんにすごい助けられた(ありがとうございます><)

みんなも作ってみると面白いよ!