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

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

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

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

ImageMagick のプログラム構造

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

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

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

% convert -resize 80x80 foo.png baa.gif




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

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

ImageMagick を build する

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

% tar xfz ImageMagick-6.7.0-7.tar.gz

% cd ImageMagick-6.7.0-7
% ./configure --without-perl --prefix=/home/yoya/test/im670
% make
% make install

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

% ls /home/yoya/test/im670
bin  etc  include  lib  share
% /home/yoya/test/im670/bin/identify php.gif 
php.gif GIF 120x67 120x67+0+0 8-bit PseudoClass 128c 2.52KB 0.000u 0:00.000

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

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

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

問題概要

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

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

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

% identify ava.gif
ava.gif[0] GIF 240x190 240x190+0+0 PseudoClass 128c 8-bit 9.375kb 
ava.gif[1] GIF 1x1 240x190+95+15 PseudoClass 2c 8-bit 9.375kb 
ava.gif[2] GIF 1x1 240x190+95+15 PseudoClass 2c 8-bit 9.375kb 
ava.gif[3] GIF 1x1 240x190+95+15 PseudoClass 2c 8-bit 9.375kb 
ava.gif[4] GIF 1x1 240x190+95+15 PseudoClass 2c 8-bit 9.375kb 
ava.gif[5] GIF 1x1 240x190+95+15 PseudoClass 2c 8-bit 9.375kb 
ava.gif[6] GIF 1x1 240x190+95+15 PseudoClass 2c 8-bit 9.375kb 
ava.gif[7] GIF 1x1 240x190+95+15 PseudoClass 2c 8-bit 9.375kb 

この 1×1 240×190+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 アニメーションを生成する時に最適化を行います。

% convert -layers optimize foo[1-3].png baa.gif
差分クリップ

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



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

透明化

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



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

表示不具合の調査結果

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

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

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

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

              case OptimizeLayer:
              {
                /*                                                              
                  General Purpose, GIF Animation Optimizer.                     
                */
                // 最適化を解いてベタな紙芝居のコマにする
                layers=CoalesceImages(*images,exception);
  <略>
                // 前フレームと差分のある部分をクリップしたコマに差し替える
                layers=OptimizeImageLayers(*images,exception);
  <略>
                // 前フレームと変化のないpixelを透明にする
                OptimizeImageTransparency(*images,exception);
  <略>
                // 透明色を含めて256色以下に減色
                (void) RemapImages(quantize_info,*images,(Image *) NULL);
                break;

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

magick/layer.c – OptimizeLayerFrames

bounds[i]=CompareImageBounds(curr->previous,curr,CompareAnyLayer,exception)
if ( bounds[i].x < 0 ) {
       ( 差分がない時の処理 )
  } 
  else
     {
        /*
          Compare a none disposal against a previous disposal
        */

       ( 差分がある時の処理 )

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

    else
      {
        /*                                                              
          Compare a none disposal against a previous disposal           
        */

       /* for AU mobile terminal */
        if ((bounds[i].width == 1) && (1 < image->columns)) {
           bounds[i].width = 2;
           if (image->columns < bounds[i].x + 2) { // 右にはみ出る場合
	      bounds[i].x --; // 左にシフト
           }
       }
       if ((bounds[i].height == 1) && (1 < image->rows)) {
           bounds[i].height = 2;
           if (image->rows < bounds[i].y + 2) { // 下にはみ出る場合
               bounds[i].y --; // 上にシフト
           }
       }

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



  • 結果
% /home/yoya/test/im/bin/convert -layers optimize [1-8].png ava.gif
% /home/yoya/test/im/bin/identify ava.gif
ava.gif[0] GIF 240x190 240x190+0+0 PseudoClass 256c 8-bit 10.4922kb 
ava.gif[1] GIF 2x2 240x190+95+15 PseudoClass 256c 8-bit 10.4922kb 
ava.gif[2] GIF 2x2 240x190+95+15 PseudoClass 256c 8-bit 10.4922kb 
ava.gif[3] GIF 2x2 240x190+95+15 PseudoClass 256c 8-bit 10.4922kb 
ava.gif[4] GIF 2x2 240x190+95+15 PseudoClass 256c 8-bit 10.4922kb 
ava.gif[5] GIF 2x2 240x190+95+15 PseudoClass 256c 8-bit 10.4922kb 
ava.gif[6] GIF 2x2 240x190+95+15 PseudoClass 256c 8-bit 10.4922kb 
ava.gif[7] GIF 2x2 240x190+95+15 PseudoClass 256c 8-bit 10.4922kb 

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

そもそも論

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

次回予告

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

Author: よや