Skip to content

Instantly share code, notes, and snippets.

@koyachi
Last active August 15, 2019 01:29
Show Gist options
  • Save koyachi/3335f68985d21612ad82233eed8cf0a8 to your computer and use it in GitHub Desktop.
Save koyachi/3335f68985d21612ad82233eed8cf0a8 to your computer and use it in GitHub Desktop.

iOSアプリ上でGIFのループ回数をいい感じに解釈する

iOSアプリでGIFアニメーションを再生してみたところ、PCのブラウザ上でのループ再生回数と異なるケースがあることがわかりました。PCのブラウザ上で動作するWebアプリケーションと同等のGIFループ再生機能を提供したかったので、なぜループ再生回数が違うのか調査し、その違いを吸収することを試みました。

具体的な事象としては、ブラウザで表示すると1回アニメーションするGIFのループ回数が、kCGImagePropertyGIFLoopCount を使ってGIFファイル中のループ回数を取得すると0が返ってきていました。 UIImageViewのanimationRepeatCountではrepeat count == 0は無限ループを意味し、このまま解釈するとiOS上では無限ループしてしまい、ブラウザと同様に再生できません。

Netscape Looping Application Extensionをどう解釈するか

GIFのフォーマットについて調べてみると、GIF内にはNetscape Looping Application Extension (GIF Unofficial Specification)という領域があり、ここにGIFのループ回数が記されていることがわかりました。

更に調べてみると、このNetscape Looping Application ExtensionはApplication Extensionを利用してNetscape社が入れた仕様で、明確なGIFの仕様ではなさそうでした。

明確なGIFの仕様ではないので、GIFプレイヤーの実装で解釈が異なることがありえそうです。GIF内にNetscape Looping Application Extensionが存在する場合の2つの解釈方法を考えてみます:

  • A: 記録されている画像フレームたちをとりあえず展開(再生)し、更にloop回数分ループする派
    • (ここでいう「画像フレーム」とは、Animation用の各フレームのことです)
  • B: loop回数分だけループする派

このAとBの解釈では、GIF上に同じループ回数が記されていても再生回数が1回分ズレることになります。

GIF内にNetscape Looping Application Extensionが存在しない場合の2つの解釈方法は次の通りです:

  • A: 記録されている画像フレームたちをとりあえず展開(再生)し、更にloop回数分ループする派
    • => loop回数(今回はextensionブロック無しなので0) + 1回ループしてるように見える
    • = 1
  • B: loop回数分だけループする派
    • => loop回数(今回はextensionブロック無しなので0)
    • = 0

macos上で動作するメジャーなブラウザでのループ回数を調べてみました(2017-06-07, コヤチ調べ)

GIF内のNetscape Looping Application Extensionブロック有無、画像フレームの有無、Extension内loop回数 Chrome Safari Firefox
ブロックなし、複数フレームなし 静止画 静止画 静止画
ブロックなし、複数フレームあり 1回再生 1回再生 1回再生
ブロックあり、loop回数=0 無限ループ 無限ループ 無限ループ
ブロックあり、loop回数=1 2回再生 1回再生 1回再生
ブロックあり、loop回数=2 3回再生 2回再生 2回再生

現在のブラウザではB派に近い(loop回数分だけループする派 + 0の場合だけ無限ループ)解釈が多いようです。

Netscape Looping Application Extensionが存在しない場合(iOS)

ここで、Netscape Looping Application Extensionが存在しない場合のiOSアプリ上でのループ回数を確認してみます。 ブラウザで見ると1回再生されるGIFのループ回数をkCGImagePropertyGIFLoopCountで参照すると0ということは、kCGImagePropertyGIFLoopCountでの解釈はB派のように見えます。これは想像ですがNetscape Looping Application Extension内に記されているループ回数を取得する、という用途に使うためのものだからだと思われます。ただ、ブラウザの挙動に似せたい場合は困ります。

そもそも、Netscape Looping Application Extensionが存在しなく、複数画像フレームのみが存在するGIF画像はありえるのでしょうか?結果から言うとありえます。あるWebサービスにアップロードしたループ回数1回のGIFをダウンロードするとそうなっていました。あってもなくてもブラウザの解釈が同じならファイルから削ってしまって、ファイルサイズ圧縮手法の一つとして使われているのではないかと思われます。

Netscape Looping Application Extensionが存在しなく複数画像フレームのみが存在する画像のループを、iOSでもブラウザ同様に解釈したい

Netscape Looping Application Extensionが存在しなく複数画像フレームのみが存在する画像のループ回数を、ブラウザと同様に解釈したいので、以下のSwiftライブラリを作りました。

このライブラリではループ回数以外の要素は真面目に解析していません。実装にあたってはいろんな言語のGIF解析ライブラリを参考にしました。GIFMetadataを使うことでUIImageView等に適切なループ回数を設定することができるようになりました。

GIFMetadataを作るにあたって初めてSwiftでバイナリ解析のコードを書きましたが、他の言語でバイナリ解析するのと遜色ない感じで書けました。このライブラリ実装時にはKaitai Structも気になりましたが、decode対象部分も限定されているので素直な実装としました。

リンク

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment