GIF アニメ生成は本当に GraphicsMagick で行うべきか?

画像配信チームのよや(@yoyapp)です。こんにちは。

最近、「ImageMagick で GIF アニメを作るのはオワコンで GraphicsMagick を使うのが情強」との言説を耳にするので、流れに乗じて *Magick による GIF アニメ生成の解説をしながら、YoyaMagick の宣伝をさせて頂きます。

はじめにまとめ

いきなり結論です。

  • 処理時間だけ気にするなら GraphicsMagick
  • 最適化でファイルサイズを減らしたいなら ImageMagick
  • 処理時間もファイルサイズも両方気にしなくっちゃ。という人には YoyaMagick

処理時間 ファイルサイズ
ImageMagick × 超遅い ○ 小さい
GraphicsMagick ◎ 超速い × 大きい
YoyaMagick ○ 速い ○ 小さい

具体的には以下のように使い分けると良いでしょう。

  • 手早く GIF アニメを作りたい > GraphicsMagick
  • Web のバックエンドで動かしたい > YoyaMagick
  • YoyaMagick が使える環境ではない > ImageMagick

自分が今まで耳にした誤解を元に、ポイントを列挙します。

  • まず、ImageMagick の GIF アニメ生成に時間がかかる場合、その処理の大半は減色処理です。

    • ImageMagick の減色は主に減色専用のデータ構造を用いる為、Q8, Q16 (*2)による性能の違いは殆どありません。
  • 実は、GIF アニメ最適化の差分フレーム抽出は、減色やGIF エンコードの時間に比べて殆ど時間が掛かりません。

    • 差分フレームが小さい程、2枚目以降の GIF 画像が小さくなり、むしろ全体として処理時間が短くなります。
  • ImageMagick に比べて、GraphicsMagick は圧倒的に速いですが、GIF アニメ最適化が出来ないので出力サイズが大きくなりがちです。(-layers optimize オプションが使えない為)
  • あと、後述の理由で GraphicsMagick は減色後の画質がよくないです。ぱっと見では分からないレベルですが、画質に拘りのある人は気にした方が良いでしょう。

パフォーマンス比較

  • 弊社のアニメーション付きアバター画像469種類(*3)をサンプルにして測定しました。

    • 8枚の PNG を元に、以下のようにGIF アニメを生成します。
# ImageMagick, YoyaMagick
convert -loop 0 -layers optimize -delay 16.7 a-[0-7].png a.gif
# GraphicsMagick
gm convert -loop 0 -delay 16.7 a-[0-7].png a.gif
  • build 時の configure はなるべくデフォルト値にしてますが、–with-quantum-depth については ImageMagick と YoyaMagick が Q16 、GraphicsMagick が Q8 なので、公平を期して Q8 に合わせました。
~/src/ImageMagick-6.8.5-6$ ./configure \
        --prefix=$HOME/ImageMagick/6.8.5-6Q8 \
        --with-quantum-depth=8 --without-perl
  • サーバでの動作を考えると –disable-openmp を付けた上で、複数スレッドでの検証をするべきですが、次回の課題とさせて下さい。

処理時間

  • 横軸は、ImageMagick の処理時間の短い画像順に並べました。
  • 縦軸は、各画像に対して5回測定した処理時間[sec]のトリム平均(異常値を排除した上での平均)です。



  • ImageMagick はとっても重たいです。
  • GraphicsMagick は YoyaMagick に比べて平均的に2割ほど速いです。

    • つまり、以下の順位になります。

GraphicsMagick > YoyaMagick >>> ImageMagick

  • 処理時間は GraphicsMagick の勝ちです。

ファイルサイズ

処理時間と同様のグラフの作り方です。

  • 横軸は、GraphicsMagick が出力したファイルのサイズが小さい画像順に並べました。
  • 縦軸は、各画像のファイルサイズ(bytes)です。



  • ImageMagick の線が見えないのは YoyaMagick と重なっている為です。
  • ImageMagick, YoyaMagick は -layers optimize が使えるので、GraphicsMagick に比べ平均的に3〜4割程度のサイズで済みます。

但し、本実験サンプルのアバター画像は差分圧縮が効き易い為に、これだけのファイルサイズの差が出るので、一般的にこういう結果になるとは限りません。
例えば、何かを撮影した動画の mp4 を元に GIF アニメを作るような例では圧縮が効き難いので、GraphicsMagick の方が良いかもしれません。
とはいえ人工的な絵のアニメーションでは、本記事と似たような結果が期待できると思います。

GIF アニメ生成のプロセス

ImageMagick では以下の処理を行います。

  • a) 画像で使っている色を256色(又は255色)以下に減らします。(減色)
  • b) コマ間で差分のあるピクセルをカバーする最小の長方形を探します。(差分クリップ)
  • c) コマ間で色が変わらないピクセルを透明色にします。 (透明化)
  • d) 各コマのヘッダとGIF エンコードした画像を並べて1つのファイルにします。(連結)

詳細は以下の記事をご参照下さい。

この内、b), c) は -layers optimize オプションの機能で、GraphicsMagick では動きません。
但し、GraphicsMagick が速いのは b), c) が動かないのとは関係ありません。

減色処理(a)のパフォーマンス

GIF はパレット形式の画像フォーマットで、パレットには最大256色しか定義出来ません。それ故、GIF アニメを作る時には、まず減色が必要になります。
そして、ImageMagick は減色処理が大変重たいのです。。

ImageMagick の減色処理

詳細は以下の記事をご参照下さい。



ImageMagick は減色処理にこの RGBA 色空間の4次元ツリーを使っていて、以下の要因で重たくなります。

  • 減色メソッドである QuantizeImage を呼ぶ度に、このツリーを一から生成&削除する。
  • 色を削る際に、このツリーに対して何度も何度も末端ノードまで辿ってツリーの中身を書き換える。

GraphicsMagick の減色処理

GraphicsMagick の GIF アニメ生成が何故速いかですが、
実は、 元になってる ImageMagick-5.5.4 が速い というオチだったりします。。

$ time ~/ImageMagick/6.8.5-6/bin/convert -layers optimize a-?.png a.gif
real	0m8.623s
user	0m10.149s
sys	0m0.116s
$ time ~/ImageMagick/5.5.4-4/bin/convert a-?.png a.gif
real	0m1.610s
user	0m1.596s
sys	0m0.016s
$ time ~/GraphicsMagick/1.3.18/bin/gm convert a-?.png a.gif
real	0m1.127s
user	0m2.004s
sys	0m0.008s

ImageMagick の各バージョンで処理時間を比較したところ、ImageMagick-6.1.3 で突然、減色処理が重たくなりました。
magick/quantize.c 自体は 6.1.2 と全く同じですが、coders/gif.c から QuantizeImage への呼び方が変わっているので、その影響と思われます。ただ、呼ぶ回数は倍増していますが、それだけでここまでの性能差は説明できないので、別の要因もありそうです。

画質の問題

ImageMagick 6.x.x のシリーズで画質改善に試行錯誤していますが、GraphicsMagick にはそれが反映されていないので注意が必要です。特にディザのかかり方に違いが見えます。

とあるアバターを変換した結果を、左側に GraphicsMagick、右側に ImageMagick で並べます。



  • GraphicsMagick で変換した左側の画像は、静止しているはずの領域もコマの進みに応じてノイズのように変化します。

更に顔を拡大すると。



  • GraphicsMagick の方は女性の顔にブツブツが出来ています。これはダメです。。

YoyaMagick の減色処理

ImageMagick を元に高速化を施しています。改造内容の詳細は以下の記事をご覧ください。




色を削る際に、このRGBA色空間のツリーを二回だけ辿れば済むようにしています。

GIF 最適化(b,c)のパフォーマンス

GIF の最適化ではコマ間で差分がある部分をカバーするように画像をクリップして、GIF 画像として持つ縦横サイズを極力減らします。



「コマ間の差分を調べる処理」に比べて「画像を GIF にエンコードする時間」の方が処理が多いので、大雑把には、(減色は最適化と関係ないので以下の式から外してます)

  • 最適化しない場合の処理時間 = 「(大きな)画像を GIF エンコードする時間」
  • 最適化する場合の処理時間 = 「コマ間の差分を調べる時間」+「(小さな)画像を GIF エンコードする時間」

として、以下のように考えられます。

  • 差分が大きい場合 => クリップしても小さくならないので最適化するとほんの少し遅くなる。
  • 差分が小さい場合 => クリップが小さいので最適化した方が速い。ファイルサイズも減って二度嬉しい。

この差分の傾向は、どういうアニメーションを対象とするかによって決まります。
GraphicsMagick が向いている場合もありますが、弊社のアバターは差分が小さく圧縮が効く事が多いので、処理時間とファイルサイズを勘案して YoyaMagick を利用しています。

さいごに

GraphicsMagick に -layers optimize 機能を入れるか、ImageMagick の減色処理が重たい場所を改造すると、最強になるかもしれません。今後の研究課題にしようと思います。

以上、皆様のお役にたてれば幸いです。

*1: 当初 6.8.5-6-y1 で公開しましたが、幾つかの問題に手当てを入れました

*2: Q8, Q16 の Q は quantum-depth の Q で、ImageMagick が画像をメモリ上に展開する時にR,G,B,A の各々を 8bit、16bit のどちらで保持するかを表します。

*3: 500種類のデータを用意したつもりが 31 個壊れていて取り除いただけで、この半端な数に深い意味はありません。

Author: よや