img要素のsrcset属性周辺のBlink(Chromium)の実装の調査

仕事で、高解像度デバイス対応、いわゆるRetina対応のためにimg要素のsrcset属性をつかおうとして、細かい仕様が気になって調査しました。

例えば、以下のように記述した場合、デバイスピクセル比が1のときはbnr_1x.png, 2のときはbnr_2x.pngが取得・表示されますが、 1.5のような半端な値の場合はどうなるのか、といった疑問です(1.5xの画像を用意してsrcsetで指定すればそれまでの話ではあるんですが)。 あちこちのブログ記事を読んだり、実際の動作を確かめたかんじでは、2xが選択されるようです。

<img src="bnr_1x.png" srcset="bnr_2x.png 2x" width="200" height="50">

仕様 ( https://html.spec.whatwg.org/multipage/embedded-content.html#select-an-image-source )を読んでも、

In a user agent-specific manner, choose one image source from source set. Let this be selected source.

と書いてあり、具体的にレンダリングエンジンがどのようなロジックで画像を選択しているか分かりません。

ということで、Blinkのコードを読んでみました。

https://code.google.com/p/chromium/codesearch#chromium/src/third_party/WebKit/Source/core/html/parser/HTMLSrcsetParser.cpp&cl=GROK&gsn=pickBestImageCandidate がsrc, srcset属性で指定された複数の画像候補から読み込むのに最適のものを選択して返すメソッド

コードを読むと実際に選択しているのはselectionLogicメソッド( https://code.google.com/p/chromium/codesearch#chromium/src/third_party/WebKit/Source/core/html/parser/HTMLSrcsetParser.cpp&cl=GROK&gsn=selectionLogic&l=329 )であることが分かります。

今回の場合は、このメソッドの以下の記述がおそらく肝です。

if (((deviceScaleFactor <= 1.0) && (deviceScaleFactor > currentDensity)) || (deviceScaleFactor >= geometricMean))
   return next;
break;

条件式右項のgeometricMeanは、
候補のdensityをa, b, c, dとし、
a < b < c < d, b < deviceScaleFactor < c
という関係が成り立っている時、√(b*c) となります。

結論として、あるデバイスピクセル値でどの画像が選択されるかは、デバイスピクセル値がgeometricMean以上か、小さいかで判断すればよいです(Blinkでは、の話ですが)。

  • デバイスピクセル比(deviceScaleFactor)が2のときは、geometricMeanが√(1*2)=1.41421356..., deviceScaleFactor >= geometricMeanが真になるので、x2の画像を選択する
  • デバイスピクセル比が1のときは、条件式は偽になるので、ループから抜けて、x1の画像を選択する
  • デバイスピクセル比が1.5のときは、1.5 > √(1*2) が真になるので、x2の画像を選択する

一方、以下のような記述の場合は

<img src="bnr_1x.png" srcset="bnr_4x.png 4x" width="200" height="50">

デバイスピクセル比が1.5のときは、1.5 > √(1*4) で偽になるので、x1の画像を選択するといった具合です。

細かい話ですが、こんなかんじの実装になっているよ、というお話でした。

Blink(Chromium)のソースコードをじっくり読むのは、最近始めたのですが、以下の点でおすすめです。

  • コード検索ページ( https://code.google.com/p/chromium/codesearch#chromium/ )がよく出来ていて、SrcsetやDevicePixelRatioのような特徴的なキーワードを入力することで、すぐにお目当ての実装を探すことができる
  • Blinkそのものが、スペックのドキュメントに沿って丁寧に実装されている、適切なクラス・メソッド・変数の命名がされているので読みやすい
  • 普段からウェブ開発をやっていれば、各種仕様をある程度は把握しているので、コードが何をやっているかイメージしやすい

一部情報が古くなっていますが、ブラウザのしくみ: 最新ウェブブラウザの内部構造 - HTML5 Rocks を読んで、ブラウザがなにをやっているのかざっくり把握したこともソースコードリーディングに役立ちました。

もちろん全ての仕様についてブラウザ、レンダリングエンジンの実装を読む必要はないと思うのですが、普段からウェブ開発に関わっている人、特にフロントエンドをやっている人は、 不思議な挙動に出会ったり、「ここどうやって実装しているんだろう」という好奇心が湧いた場合は(もちろん、太刀打ちできない場合もあるかもしれませんが...)読んでみるとよいかもしれません。