可変ピクセル密度向けの高 DPI 画像

現在の複雑なデバイス環境の特徴の一つは、非常に幅広い画面ピクセル密度が利用可能であることです。デバイスによっては非常に高解像度のディスプレイを搭載しているものもあれば、そうでないものもあります。アプリケーション デベロッパーは、さまざまなピクセル密度をサポートする必要がありますが、これは非常に困難な場合があります。モバイルウェブでは、次のような要因によって課題が複雑になります。

  • さまざまなフォーム ファクタを持つ多種多様なデバイス。
  • ネットワーク帯域幅とバッテリー駆動時間が制限される。

画像に関して、ウェブアプリ デベロッパーの目標は、最高品質の画像を可能な限り効率的に配信することです。この記事では、現在と近い将来に役立つテクニックをいくつか紹介します。

可能であれば画像は使用しないでください

この問題に取り組む前に、ウェブには解像度や DPI に依存しない強力なテクノロジーが数多く存在することを覚えておいてください。具体的には、テキスト、SVG、ほとんどの CSS は、ウェブの自動ピクセル スケーリング機能(devicePixelRatio 経由)により「そのまま機能」します。

ただし、ラスター画像を避けられないこともあります。たとえば、純粋な SVG/CSS で複製するのが非常に難しいアセットが提供された場合や、写真を取り扱っている場合などです。画像を SVG に変換することはできますが、写真のベクター化は意味がありません。通常、拡大したバージョンは見栄えがよくありません。

背景

ディスプレイの密度の歴史

初期のコンピュータ ディスプレイのピクセル密度は 72 dpi または 96 dpi(1 インチあたりのドット数)でした。

ディスプレイの画素密度は、主にモバイル ユースケースを重視して徐々に向上してきました。モバイル ユースケースでは、ユーザーは通常スマートフォンを顔の近くに持ってきて、ピクセルをよりはっきりと見えるようにします。2008 年までに、150 dpi のスマートフォンが新しい標準になりました。ディスプレイの密度を高める傾向は続き、今日の新しいスマートフォンには 300 dpi のディスプレイ(Apple では「Retina」というブランド名)が搭載されています。

もちろん、ピクセルが完全に目立たないディスプレイが理想です。スマートフォンのフォーム ファクタの場合、現在の世代の Retina/HiDPI ディスプレイは理想に近いと言えます。ただし、Project Glass などの新しいクラスのハードウェアやウェアラブルでは、引き続き画素密度が向上していくと考えられます。

実際には、低密度画像は新しい画面でも古い画面でも同じに見えますが、ユーザーが慣れている高密度画像の鮮明な画像と比較すると、低密度画像は不自然で粗く見えます。次の図は、2x ディスプレイに 1x 画像が表示された場合の概略的なシミュレーションです。一方、2 倍の画像は非常に良好です。

Baboon 1x
Baboon 2x
ピクセル密度が異なるバブーン

ウェブ上のピクセル

ウェブが設計されたとき、ディスプレイの 99% は 96 dpi でした(またはそのように偽装されていました)。この点に関する規定はほとんどありませんでした。画面サイズと画面密度が大きく異なるため、さまざまな画面密度と寸法で画像を美しく表示するための標準的な方法が必要でした。

HTML 仕様では最近、メーカーが CSS ピクセルのサイズを決定するために使用する参照ピクセルを定義することで、この問題に対処しました。

メーカーはリファレンス ピクセルを使用して、標準ピクセルまたは理想的なピクセルに相対するデバイスの物理ピクセルのサイズを決定できます。この比率をデバイスのピクセル比と呼びます。

デバイスのピクセル比の計算

スマートフォンの画面の物理的なピクセルサイズが 180 ppi であるとします。デバイスのピクセル比の計算は、次の 3 つのステップで行います。

  1. デバイスを保持している実際の距離と、参照ピクセルの距離を比較します。

    仕様では、28 インチで 1 インチあたり 96 ピクセルが理想的とされています。ただし、スマートフォンはノートパソコンよりも顔に近づけて持つため、この距離を 18 インチと見積もります。

  2. 距離比を標準密度(96 ppi)に掛けて、指定した距離に最適なピクセル密度を取得します。

    idealPixelDensity = (28/18) * 96 = 150 ppi(約)

  3. 物理ピクセル密度と理想的なピクセル密度の比率を計算して、デバイスのピクセル比を取得します。

    devicePixelRatio = 180/150 = 1.2

devicePixelRatio の計算方法。
devicePixelRatio の計算方法を説明するために、1 つの参照角度ピクセルを示す図。

そのため、ブラウザが理想的な解像度または標準解像度に応じて画面に収まるように画像のサイズを変更する方法を知る必要がある場合、ブラウザはデバイスのピクセル比 1.2 を参照します。これは、理想的なピクセルごとに、このデバイスには 1.2 個の物理ピクセルがあることを意味します。理想的なピクセル(ウェブ仕様で定義)と物理的なピクセル(デバイス画面上のドット)を変換する式は次のとおりです。

physicalPixels = window.devicePixelRatio * idealPixels

これまで、デバイス ベンダーは devicePixelRatios(DPR)を四捨五入する傾向がありました。Apple の iPhone と iPad は DPR が 1 で、Retina 相当のデバイスは 2 です。CSS 仕様では、

ピクセル単位は、参照ピクセルに最も近いデバイス ピクセルの整数値を指します。

比率を丸めるとサブピクセル アーティファクトが少なくなる可能性があるため、丸い比率の方が優れている場合があります。

ただし、実際のデバイス環境ははるかに多様であり、Android スマートフォンの DPR は 1.5 であることが多いです。Nexus 7 タブレットの DPR は約 1.33 です。これは、上記と同様の計算によって導き出されました。今後、DPR が変動するデバイスはさらに増える予定です。そのため、クライアントの DPR が整数であると想定してはいけません。

HiDPI 画像手法の概要

最高品質の画像をできるだけ早く表示する問題を解決するための手法は数多くありますが、大きく 2 つのカテゴリに分けられます。

  1. 単一の画像の最適化、
  2. 複数の画像の選択の最適化。

単一画像アプローチ: 1 つの画像を使用するが、巧妙に処理する。これらのアプローチには、低 DPI の古いデバイスでも HiDPI 画像をダウンロードするため、パフォーマンスが犠牲になるという欠点があります。単一画像の場合のアプローチは次のとおりです。

  • 圧縮率の高い HiDPI 画像
  • 素晴らしい画像形式
  • プログレッシブ イメージ形式

複数の画像アプローチ: 複数の画像を使用しますが、読み込む画像を賢く選択します。これらのアプローチには、同じアセットの複数のバージョンを作成し、意思決定戦略を策定するという、デベロッパーに固有のオーバーヘッドがあります。指定できる広告タイプは次のとおりです。

  • JavaScript
  • サーバーサイド配信
  • CSS メディアクエリ
  • ブラウザの組み込み機能(image-set()<img srcset>

圧縮率の高い HiDPI 画像

画像は、平均的なウェブサイトのダウンロードに費やされる帯域幅の 60% を占めています。すべてのクライアントに HiDPI 画像を提供することで、この数を増やすことができます。どれくらい大きくなりますか?

いくつかのテストを行い、JPEG 品質を 90、50、20 に設定して 1x と 2x の画像フラグメントを生成しました。以下は、これらの画像を生成するために使用したシェル スクリプトImageMagick を使用)です。

タイル例 1。 タイルの例 2。 タイル例 3。 さまざまな圧縮とピクセル密度での画像のサンプル。

この小さな非科学的なサンプリングから、大きな画像を圧縮すると、品質とサイズのバランスが取れているようです。私から見ると、圧縮率の高い 2 倍の画像は、非圧縮の 1 倍の画像よりも見栄えがします。

もちろん、低画質で圧縮率の高い 2x 画像を 2x デバイスに配信することは、高画質の画像を配信するよりも劣ります。上記のアプローチでは、画質のペナルティが発生します。画質: 90 枚の画像と画質: 20 枚の画像を比較すると、鮮明さが低下し、粗さが目立つようになります。これらのアーティファクトは、高品質の画像が重要な場合(写真ビューア アプリケーションなど)や、妥協を許さないアプリ デベロッパーには許容されない場合があります。

上記の比較は、すべて圧縮された JPEG で行われました。広く実装されている画像形式(JPEG、PNG、GIF)には多くのトレードオフがあることに注意してください。

素晴らしい画像形式

WebP は、画像の忠実度を維持しながら非常に優れた圧縮を実現する、非常に魅力的な画像形式です。もちろん、まだすべてのデバイスに実装されているわけではありません

たとえば、JavaScript を使用して WebP のサポートを確認する方法があります。data-uri を使用して 1 ピクセルの画像を読み込み、読み込みイベントまたはエラー イベントが発生するのを待ってから、サイズが正しいことを確認します。Modernizr には、Modernizr.webp から利用できるこのような機能検出スクリプトが付属しています。

ただし、image() 関数を使用して CSS で直接行う方が適切です。WebP 画像と JPEG フォールバックがある場合は、次のように記述します。

#pic {
  background: image("foo.webp", "foo.jpg");
}

このアプローチにはいくつかの問題があります。まず、image() は広く実装されていません。2 つ目の理由は、WebP 圧縮は JPEG を圧倒的に上回るものの、このWebP ギャラリーに基づくと、サイズは約 30% 小さくなる程度で、比較的小さな改善にすぎません。したがって、高 DPI の問題に対処するには WebP だけでは不十分です。

プログレッシブ イメージ形式

JPEG 2000、プログレッシブ JPEG、プログレッシブ PNG、GIF などのプログレッシブ画像形式には、画像が完全に読み込まれる前に画像が表示されるというメリットがあります(議論の余地があります)。サイズのオーバーヘッドが発生する可能性がありますが、この点については矛盾する証拠があります。プログレッシブ モードでは「PNG 画像のサイズが約 20%、JPEG 画像と GIF 画像のサイズが約 10% 増加する」と Jeff Atwood 氏は主張しています。ただし、Stoyan Stefanov は、大規模なファイルの場合、プログレッシブ モードの方が(ほとんどの場合)効率的であると主張しています。

一見すると、プログレッシブ イメージは、最高品質の画像をできるだけ早く提供するという観点から非常に有望な技術に見えます。追加データによって画質が向上しない(忠実度が向上する部分がすべてサブピクセル単位である)ことが判明したら、ブラウザは画像のダウンロードとデコードを停止できます。

接続は簡単に終了できますが、再起動にはコストがかかる場合があります。画像が多いサイトでは、1 つの HTTP 接続を存続させ、できるだけ長く再利用するのが最も効率的な方法です。1 つの画像のダウンロードが完了したために接続が早期に終了した場合、ブラウザは新しい接続を作成する必要があります。これは、低レイテンシ環境では非常に遅い場合があります。

この問題を回避する 1 つの方法は、HTTP Range リクエストを使用することです。これにより、ブラウザは取得するバイト範囲を指定できます。スマート ブラウザは、HEAD リクエストを送信してヘッダーを取得し、処理して、実際に必要な画像の量を決定してから取得できます。残念ながら、HTTP 範囲はウェブサーバーで十分にサポートされていないため、このアプローチは実用的ではありません。

最後に、このアプローチの明らかな制限は、読み込む画像を選択できないこと、同じ画像の忠実度のみを変更できることです。そのため、この機能は「アート ディレクション」のユースケースには対応していません。

JavaScript を使用して読み込む画像を決定する

読み込む画像を決定する最初の、最も明白な方法は、クライアントで JavaScript を使用することです。この方法では、ユーザー エージェントに関するすべての情報を把握し、適切な対応を取ることができます。window.devicePixelRatio を使用してデバイスのピクセル比を特定したり、画面の幅と高さを取得したりできます。また、navigator.connection を使用してネットワーク接続のスニッフィングを行うことも、foresight.js ライブラリのように偽のリクエストを発行することもできます。これらの情報をすべて収集したら、読み込む画像を決定できます。

上記のようなことを実行する 100 万個の JavaScript ライブラリがありますが、残念ながら、どれも特に優れたものではありません。

このアプローチの大きな欠点の 1 つは、JavaScript を使用すると、先読みパーサーが完了するまで画像の読み込みが遅れることです。つまり、pageload イベントが発生するまで、イメージのダウンロードは開始されません。詳しくは、Jason Grigsby の記事をご覧ください。

サーバーに読み込む画像を決定する

サーバーサイドで決定を遅らせるには、提供する画像ごとにカスタム リクエスト ハンドラを作成します。このようなハンドラは、User-Agent(サーバーにリレーされる唯一の情報)に基づいて Retina のサポートを確認します。次に、サーバーサイド ロジックで HiDPI アセットを提供するかどうかに基づいて、適切なアセット(既知の規則に従って命名されたアセット)を読み込みます。

残念ながら、User-Agent から、デバイスで高画質または低画質の画像を受け取るべきかどうかを判断するのに十分な情報が得られるとは限りません。また、User-Agent に関連するものはすべてハックであるため、可能な限り使用しないことをおすすめします。

CSS メディアクエリを使用する

CSS メディアクエリは宣言型であるため、意図を明記し、ブラウザに適切な処理を任せることができます。メディアクエリの最も一般的な用途であるデバイスサイズの一致に加えて、devicePixelRatio を一致させることもできます。関連するメディアクエリは device-pixel-ratio で、当然ながら最小値と最大値のバリエーションが関連付けられています。高 DPI 画像を読み込み、デバイスのピクセル比がしきい値を超える場合は、次の方法があります。

#my-image { background: (low.png); }

@media only screen and (min-device-pixel-ratio: 1.5) {
  #my-image { background: (high.png); }
}

ベンダーのプレフィックスがすべて混在すると、さらに複雑になります。特に、「min」と「max」のプレフィックスの配置の違いが原因です。

@media only screen and (min--moz-device-pixel-ratio: 1.5),
    (-o-min-device-pixel-ratio: 3/2),
    (-webkit-min-device-pixel-ratio: 1.5),
    (min-device-pixel-ratio: 1.5) {

  #my-image {
    background:url(high.png);
  }
}

このアプローチでは、JS ソリューションで失われた先読み解析のメリットを再び利用できます。また、レスポンシブ ブレークポイントを選択する柔軟性も得られます(低 DPI、中 DPI、高 DPI の画像など)。これは、サーバーサイド アプローチでは不可能でした。

残念ながら、まだ使い勝手が悪く、CSS の見た目が奇妙になる(または前処理が必要になる)可能性があります。また、このアプローチは CSS プロパティに制限されているため、<img src> を設定することはできず、画像はすべて背景のある要素である必要があります。最後に、デバイスのピクセル比に厳密に依存すると、ハイ DPI スマートフォンが EDGE 接続中に巨大な 2x 画像アセットをダウンロードする結果になる可能性があります。これは最適なユーザー エクスペリエンスではありません。

新しいブラウザ機能を利用する

最近、高 DPI 画像の問題に対するウェブ プラットフォームのサポートについて多くの議論が行われています。Apple は最近、WebKit に image-set() CSS 関数を導入しました。そのため、Safari と Chrome の両方がこれをサポートしています。CSS 関数であるため、image-set()<img> タグの問題に対処しません。@srcset を使用すると、この問題に対処できますが、(この記事の執筆時点では)リファレンス実装はありません(まだ)。次のセクションでは、image-setsrcset について詳しく説明します。

高 DPI をサポートするブラウザ機能

最終的には、どのアプローチを取るかは、特定の要件によって異なります。ただし、前述のすべてのアプローチには欠点があります。ただし、image-set と srcset が広くサポートされるようになると、この問題の適切な解決策となります。現時点では、その理想的な未来にできるだけ近づけるベスト プラクティスをいくつかご紹介します。

まず、この 2 つの違いは何でしょうか?image-set() は CSS 関数で、background CSS プロパティの値として使用できます。srcset は <img> 要素に固有の属性で、構文は似ています。どちらのタグでも画像宣言を指定できますが、srcset 属性を使用すると、ビューポートのサイズに基づいて読み込む画像を構成することもできます。

画像セットのベスト プラクティス

image-set() CSS 関数は、-webkit-image-set() という接頭辞で使用できます。構文は非常にシンプルで、1 つ以上の画像宣言をカンマで区切って指定します。画像宣言は、URL 文字列または url() 関数で構成され、その後に関連する解像度が続きます。次に例を示します。

background-image:  -webkit-image-set(
  url(icon1x.jpg) 1x,
  url(icon2x.jpg) 2x
);

これは、選択できる画像が 2 つあることをブラウザに伝えます。1 つは 1x ディスプレイ用に、もう 1 つは 2x ディスプレイ用に最適化されています。ブラウザは、さまざまな要素に基づいてどちらを読み込むかを選択します。ブラウザが十分にスマートであれば、ネットワーク速度も考慮されます(現在のところ実装されていません)。

ブラウザは正しい画像を読み込むだけでなく、適切に画像をスケーリングします。つまり、ブラウザは 2 倍の画像は 1 倍の画像の 2 倍の大きさであると想定し、2 倍の画像を 2 倍に縮小して、ページ上で同じサイズに見えるようにします。

1x、1.5x、Nx を指定する代わりに、特定のデバイスの dpi ピクセル密度を指定することもできます。

これは、image-set プロパティをサポートしていないブラウザを除き、うまく機能します。そのようなブラウザでは、画像はまったく表示されません。これは明らかに望ましくないため、フォールバック(または一連のフォールバック)を使用してこの問題に対処する必要があります。

background-image: url(icon1x.jpg);
background-image: -webkit-image-set(
  url(icon1x.jpg) 1x,
  url(icon2x.jpg) 2x
);
/* This will be useful if image-set gets into the platform, unprefixed.
    Also include other prefixed versions of this */
background-image: image-set(
  url(icon1x.jpg) 1x,
  url(icon2x.jpg) 2x
);

上記のコードでは、image-set をサポートしているブラウザでは適切なアセットが読み込まれ、そうでない場合は 1x アセットにフォールバックします。ただし、image-set() のブラウザ サポートは低く、ほとんどのユーザー エージェントは 1x アセットを取得します。

このデモでは、image-set() を使用して正しい画像を読み込み、この CSS 関数がサポートされていない場合は 1x アセットにフォールバックします。

ここで、image-set() をポリフィル(JavaScript シムを作成)して、それで終わりにしない理由について説明します。結局のところ、CSS 関数に効率的なポリフィルを実装するのは非常に困難です。(理由の詳細については、この www スタイルのディスカッションをご覧ください)。

画像の srcset

srcset の例を以下に示します。

<img alt="my awesome image"
  src="banner.jpeg"
  srcset="banner-HD.jpeg 2x, banner-phone.jpeg 640w, banner-phone-HD.jpeg 640w 2x">

ご覧のとおり、srcset 要素は、image-set が提供する x 宣言に加えて、ビューポートのサイズに対応する w 値と h 値も取り、最も関連性の高いバージョンを配信しようとします。上記の設定では、ビューポートの幅が 640 px 未満のデバイスには banner-phone.jpeg、小画面の高 DPI デバイスには banner-phone-HD.jpeg、画面が 640 px を超える高 DPI デバイスには banner-HD.jpeg、それ以外のデバイスには banner.jpeg が配信されます。

画像要素に image-set を使用する

ほとんどのブラウザでは img 要素の srcset 属性が実装されていないため、img 要素を背景付きの <div> に置き換えて、image-set アプローチを使用するのが魅力的かもしれません。ただし、いくつかの制限があります。欠点は、<img> タグに長時間のセマンティックな値があることです。実際には、これは主にウェブクロールとユーザー補助の理由で重要です。

-webkit-image-set を使用する場合は、background CSS プロパティを使用することをおすすめします。このアプローチの欠点は、画像サイズを指定する必要があることですが、1x 以外の画像を使用している場合は不明です。代わりに、次のように content CSS プロパティを使用できます。

<div id="my-content-image"
  style="content: -webkit-image-set(
    url(icon1x.jpg) 1x,
    url(icon2x.jpg) 2x);">
</div>

これにより、devicePixelRatio に基づいて画像が自動的にスケーリングされます。上記の手法の実装例については、こちらの例をご覧ください。image-set をサポートしていないブラウザ向けに url() への追加のフォールバックも用意されています。

srcset のポリフィル

srcset の便利な機能の一つは、自然なフォールバックが付属していることです。srcset 属性が実装されていない場合、すべてのブラウザは src 属性を処理します。また、HTML 属性にすぎないため、JavaScript でポリフィルを作成することもできます。

このポリフィルには、できるだけ仕様に近づけるように単体テストが付属しています。また、srcset がネイティブに実装されている場合、ポリフィルがコードを実行できないようにするチェックも用意されています。

以下は、ポリフィルのデモです。

まとめ

高 DPI 画像の問題を解決する魔法の解決策はありません。

最も簡単な解決策は、画像を完全に回避し、代わりに SVG と CSS を使用することです。ただし、サイトに高画質の画像がある場合は、必ずしも現実的とは限りません。

JS、CSS、サーバーサイドを使用するアプローチにはそれぞれ長所と短所があります。ただし、最も有望なアプローチは、新しいブラウザ機能を活用することです。image-setsrcset のブラウザ サポートはまだ不完全ですが、今すぐ使用できる妥当な代替手段があります。

まとめると、推奨事項は次のとおりです。