10msより20msのGIFが速い理由

Published:

まずはこちらをごらんください。

なぜか10msと100msが同じで、20msが一番速く見えていますね? 主要ブラウザーはいずれも「10ms以下のフレームディレイを100msに丸める」処理を持っているためです。

各ブラウザーの実装

本当にそうなっているのか、ソースを見てみましょう。

WebKit(Safari)とBlink(Chrome、Edge)

WebKitでは ImageDecoderCG.cppに実装があります。

// Many annoying ads specify a 0 duration to make an image flash as quickly as possible.
// We follow Firefox's behavior and use a duration of 100 ms for any frames that specify
// a duration of <= 10 ms. See <rdar://problem/7689300> and <http://webkit.org/b/36082>
// for more information.
if (duration < 11_ms)
    return 100_ms;

意訳すると「画像を高速に点滅させる迷惑広告は0msに設定しているので、Firefoxの挙動にしたがって10ms以下は100msにする」。

WebKitのフォークであるBlinkでは、同じコメント付きで deferred_image_decoder.ccに実装があります。

// Many annoying ads specify a 0 duration to make an image flash as quickly as
// possible. We follow Firefox's behavior and use a duration of 100 ms for any
// frames that specify a duration of <= 10 ms. See <rdar://problem/7689300>
// and <http://webkit.org/b/36082> for more information.
if (duration <= base::Milliseconds(10))
    duration = base::Milliseconds(100);

Gecko(Firefox)

では、名指しされているFirefoxの実装はどうでしょうか。 FrameTimeout.hに何やら長いコメントと共に実装があります。

// Very small timeout values are problematic for two reasons: we don't want
// to burn energy redrawing animated images extremely fast, and broken tools
// generate these values when they actually want a "default" value, so such
// images won't play back right without normalization. For some context,
// see bug 890743, bug 125137, bug 139677, and bug 207059. The historical
// behavior of IE and Opera was:
//   IE 6/Win:
//     10 - 50ms is normalized to 100ms.
//     >50ms is used unnormalized.
//   Opera 7 final/Win:
//     10ms is normalized to 100ms.
//     >10ms is used unnormalized.
if (aRawMilliseconds >= 0 && aRawMilliseconds <= 10) {
  return FrameTimeout(100);
}

意訳すると「極端に速い再生はエネルギーを浪費するうえ、壊れたツールがデフォルト値のつもりで小さい値を生成するので、正規化しないと正しく再生できない」。

補正はどうやって決まったのか

では、この補正はどのように決まったのでしょうか。 コメントに書かれているBugzillaに経緯が書いてあります。

この補正は2002年の Bug 125137まで遡ります。

CPUの負荷問題

ことの発端は、フレームディレイが極端に短いとCPUが100%になり、フリーズするという問題です。 それと同時に、実はIEが黙って100ms程度に丸めていることも判明します。 GIF制作者が極端に短い値を設定してもIEでは丸めて再生されるため、意図どおりの速度と誤認したまま公開しているという実態が明らかになります。

CPU問題を解決するため、100msと報告されていたIEの下限に合わせた「100ms未満を100msに丸める」処理が導入されることになります。

IEの挙動が実は違った

これで解決、かと思いきや、丸めたことによる次のような不満が Bug 139677に上がります。

  • Mac版IEやiCabは指定どおり再生
  • Mozillaだけアニメが遅く見える
  • GIFの指定値を勝手に上書きするな

また Bug 207059では、60ms間隔で作ったGIFまで巻き添えで遅くなる実例が報告されます。

ここでブラウザーごとの挙動が実測されます( c11c13)。 その結果、IEは10~50msのときに100msに丸めることが判明します。 また、Operaでは10ms以下だけ100msに丸めるようになっていました。

これを受けて、Opera方式の「10ms以下を100msに丸める」が採用され、2004年に Bug 207059で実装されます。

WebKitの対応

WebKitも当初はFirefoxと同じ「10ms以下を100msに丸める」でしたが、2007年に Bug 14413でIEに合わせた「50ms以下を100msに丸める」に変更されていました。

この変更により、同じGIFなのにSafariで遅く見えることが Bug 26455に上がります。 そして2010年に、Firefoxが長年この仕様であり互換性の問題は起きにくいとして「10ms以下を100msに丸める」方式に戻しました。

Chromiumはこのコードをコメントごと継承しました。 アニメーション画像のディレイの扱いを定めた規定仕様はないため、各エンジンが互いに挙動をそろえる形で収束したことになります。

その後の騒動

Bug 232822では、仕様違反だという指摘が上がりましたが、却下されました。

Bug 890743では、1ループの0msは尊重してほしいとされ実際に採用されましたが、その後 Bug 1126330で元に戻されました。

回避策

10ms以下を100msに丸める処理は画像フォーマット共通の実装になっているため、GIFに限らずAPNGやアニメーションWebPも対象です。 一方で、11ms以上は丸め処理の対象外です。 GIFのディレイは10ms刻みでしか指定できないため、GIFで丸めを回避できる最小値は実質20msになります。 1ms単位で指定できるアニメーションWebPや、秒を分数で指定できるAPNGなら、11msから丸めを回避できます。

どうしても10ms以下にする場合は、video要素で動画にする、JavaScriptやCSSで切り替えるなどの方法が考えられます。