ImageMagick 改造入門 (その壱) GIFアニメーション

こんにちは。ミドルウェア開発チームのよやです。

今回は、ImageMagick についてお話します。

ImageMagick は高機能で大変便利な画像処理ツールです。弊社でも利用させて頂いていますが、稀に実サービスにそのまま適用出来ないケースがあります。
そこで、困った時に ImageMagick 自体を改造する際のポイントと、実際の応用例をご紹介します。

ImageMagick のプログラム構造

ImageMagick のプログラムは主に以下のディレクトリに分かれます。(Magick+ ディレクトリ等幾つかは割愛します)

  • utilities/<コマンド名>.c コマンドラインツールの起点(main 関数)
  • wand/〜.c (コマンド共通処理とコマンド毎の処理、Wand API)
  • magick/〜.c (機能モジュール、ユーティリティ)
  • coders/〜.c (ファイル形式処理)

例えば、convert コマンドで PNG ファイルをリサイズして GIF ファイルとして保存する場合は、大まかには以下のような経路で動作します。




※ quantize.c は減色処理モジュールです。

コマンドライン引数やライブラリ API を変更したい場合は別ですが、軽く機能を改造しようといった場合は magick/ 以下を触る事が多いと思います。今回ご紹介する改造例も、magick/ 以下のファイルを編集します。

ImageMagick を build する

ImageMagick の build は Linux や MacOS 上では簡単です。
※ zlib, libpng, jpeglib のライブラリは最低限必要なので事前にインストールして下さい。

等のように prefix 指定の configure を実行して、make install すると、

このように指定した場所に実行関連ファイルが展開され、ここの bin 以下のコマンドは実行が可能です。尚、--without-perl が無いと make install で root 権限が必要なディレクトリにファイルを置こうとするので注意して下さい。

build さえ出来れば、後は好きにプログラムを弄って気が済むまで build し直して動作を確認していくだけですね!

GIF アニメーションの差分フレーム問題

問題概要

ある日、以下のGIF画像が特定の携帯端末で表示できないとの問い合わせを頂きました。

実際に、特定の端末で以下のように表示されるのを確認しました。(携帯端末でのみ発生する表示不具合を PC 上で再現する為に GIF のバイナリを編集してます)

一見、何でもない静止 GIF 画像に見えますが、ImageMagick の identify コマンドを使うと、GIF アニメーションである事が分かります。

この 1x1 240x190+95+15 という情報の前提知識として GIF アニメーションの概要を説明します。

GIF アニメーション

  • GIF 画像は Screen と Image に分けて考えます。



  • まず、表示領域として Screen の大きさ (width x height) を決めます。
  • その上で、Screen 内の特定領域 (Image の left, top, width, height) を指定して画像を描画します。



  • 静止画の GIF は通常、同じサイズの Screen と Image が1つずつ存在します。
  • 最適化しない GIF アニメーションは、同じサイズの Screen と Image(xコマ数)が存在します。上の図の左側のイメージです。(同じサイズの Image で上書き)
  • 最適化した GIF アニメーションは、上の図の右側のようなイメージになります。(差分のある部分だけ Image で上書き)

GIF アニメーションの最適化

ImageMagick の convert コマンドに -layers optimize を引数として与えると、GIF アニメーションを生成する時に最適化を行います。

差分クリップ

まず差分のある部分をクリップして、そこだけ画像データとして持ちます。



先程 identify で出力された 1x1 240x190+95+15 は Screen 上 の (95, 15) の位置から 1x1 サイズの領域を差分フレームとして持つという意味です。

透明化

次に、前のコマと変化のない pixel があれば透明色にします。



GIF は最終的に画像を圧縮して格納するので、単一色(この場合は透明色)の場所が多い程、GIF ファイルのサイズが小さくなりお得です。

表示不具合の調査結果

原因
  • 特定の携帯端末において、

    • GIF アニメーションの差分フレームの大きさが 2x2 未満 (1x1, 1x2, 2x1 の3パターン) の時に、正しく GIF アニメーションを表示出来ない。
    • 具体的には (dispose=1 なのに) dispose=2 相当のレンダリングを行う不具合が存在すると思われる。(dispose はコマが進む時に画像をどう上書きするかを示す番号。詳しくはspec-gif89a.txt を参照)
対応
  • GIF アニメーションの差分フレームの大きさが 2x2 未満の時に、2x2 以上になるよう拡げる。

差分フレーム枠処理の改造

コマンドラインのオプション処理は wand/mogriry.c にまとまっていて、optimize の文字列で探すと -layers optimize を処理する以下のコードが見つかります。
wand/mogrify.c - MagickCommandGenesis

OptimizeImageLayers から OptimizeLayerFrames と辿った先に、差分フレームをクリップする処理があります。

magick/layer.c - OptimizeLayerFrames

ですので、bounds の width, height を以下のように拡張すれば良いはず。

このように width, height が 1 の時に 2 に拡張する処理を入れます。
一番右や下の pixel を右下に拡張すると Screen からはみ出てしまう事への配慮で、左上にシフトする処理も入れます。



  • 結果

これで、差分フレームの大きさが 2x2 以上である事が保障される為、1x1 で表示が壊れる端末でも問題なく表示出来るようになります。

そもそも論

今回例に挙げたアバター画像の場合は、コマ間の差分抽出ロジックを弄って 1 pixel の差分は無かった事にした方が良いです。
ただ、その 1 pixel がユーザにとって大切な色である可能性も考えられるので、1 pixel の差分もアニメーション出来るようにしたい。という想いでこの改造をサービスに適用しました。

次回予告

これとは別に、GIF アニメーションの生成時間を短かくする為、減色処理の高速化も行っています。
次回は、そちらの例をご紹介する予定です。