画像は、サイズが大きい(大きくなりがちな)コンテンツの一つです。 最近は画像を使わないページはほとんどなく、むしろ使う量はどんどん増えているんじゃないでしょうか。また、HiDPI対応などでサイズも増えつつあるでしょう。 Googleの調査では、現在Web上のトラフィックの65%を画像が占めているそうです。Introで解説したように、パフォーマンスを考えるとファイルサイズは「なるべく小さく」が望ましいため、画像は通常圧縮された形式で使用されます。
WebPは、Googleが開発した圧縮形式で、従来の形式よりもファイルサイズを小さくすることを目的としています。今回はそんなWebPの概要と使い方、注意点を取り上げます。
画像圧縮とは?
ここで対象としているのはピクセル形式の画像で、ざっくり言うと「1pxごとにそこが何色なのか」という情報を持つ形式です。 例えば、Windowsのペイントなどで作れるBitmap(.bmp)という形式がなじみ深いでしょうか。
ところが、例えば以下の画像を見て下さい、同じ色が連続している場所があります。 そこで、「このピクセルは赤、このピクセルも赤…」と繰り返す情報を、「ここからここまで赤」と置き換えると、同じ情報を小さく表せるはずです。
このように、同じ情報を小さく表現する方法を圧縮といいます。これを画像に適用したのが画像圧縮です。 上の図の方法は逆の計算をすれば元の画像が完全に復元できるため、「可逆圧縮(lossless)」といいます。
また、写真などにはものすごく細かい色の変化もあり、なくなっても人間は気づかれない場合が多いので、こうしたデータを捨てることで圧縮率を上げる方法もとられます。代わりに、完全には元に戻せなくなるため、この方法は「非可逆圧縮」と呼ばれます。
この例は、あくまでもイメージで、実際には同じ情報を小さく表わす方法はいろいろとあり、それにより圧縮形式が異なります。 次によく使われる圧縮形式を見てみましょう。
圧縮形式
Webの世界では、現在以下の三つがよく使われています。
- JPEG: 写真など色数の多いものに有効。非可逆圧縮。
- GIF: 色数が少ないアイコンなどに有効、アニメーションが可能。可逆圧縮(*1)。
- PNG: アイコンなどに使われ、透過も可能。可逆圧縮。
現状はこれらの形式を場合に応じて使い分けているわけですが、Googleは多くの場合にこれらの形式よりも小さく圧縮でき、透過やアニメーションなどの機能もそろった新しい圧縮形式を開発しました。それが、WebPです。
*1: 非可逆も可能です。
WebP
読み方は、Weppy=ウェッピーです。 ファイルの構造としては、同じくGoogleが持つVP8コーデックを用いたWebMという動画フォーマットをベースとしており、WebMの1フレームだけを切り出したものがWebPで、それをRIFFという軽量なコンテナに格納した形式になっています。
https://developers.google.com/speed/webp/
WebPは以下のような特徴があります。
- 可逆 / 非可逆圧縮が可能
- 透過 (アルファチャネル) が可能
- 写真の圧縮にも向いている
- アニメーションが可能
- カラープロファイル対応
- メタデータを持たせることができる
画像に求められる基本的な機能をひと通り備えており、一つのフォーマットで幅広い用途で利用できるように作られていることがわかります。 現在Googleのサービスでは、Gmail、Google Drive、Picasa、Play Magazines、Image Search、YouTube、Chrome Web Storeなどがサポートしており、Facebookもサポートを始めました。
ブラウザ対応
現在、以下のブラウザがWebPに対応*2しています。
- Chrome
- Opera (12.0~)
- Android Browser (4.2~)
- Chrome for Android
Firefoxでは議論中で、IEやSafariは対応していません。 こうしたクライアントを対象とする場合は、後ほど説明するフォールバックが必要になります。
ツール
WebPは以下のような公式のライブラリ、オーサリングツールなどで生成することができます。
しかし、FacebookがWebPをサポートしたところ、ユーザから「画像をダウンロードしたら、編集ソフトで編集できなかった」というクレームがあったというニュースがありました。
これは、上記のようなメジャーなものを除いて、まだWebPに対応していないオーサリングツールが多くあることを意味しています。 また、OSのデフォルトのプレビューでは見れないため、Webから落とした後のことも考えるのであれば、代替手段として同じ画像のPNGやJPEGをダウンロードする手段を用意することが望ましいでしょう。
WebP Library
今回はWebP公式ライブラリに同梱されたCLIツールを用いて、実際に画像をWebPに変換してみます。 公式ライブラリは以下のページで公開されています。
Downloading and Installing WebP
ここには以下のツールと、開発用のライブラリが含まれます。
- cwebp: WebP への変換
- dwebp: WebP からの変換
- vwebp: WebP の表示
- webpmux: アニメーションの作成
- gif2webp: GIF から WebP への変換
これらのツールはかなりたくさんのオプションがあります。詳細はドキュメントを参照ください。
ここでは、使い方の解説を兼ねてPNG、JPEG、GIFとの簡単な比較をしてみます。(ライセンス情報は末尾に記載)
あくまで簡単な実験です、より厳密かつ詳細な比較結果は、こちらを参照ください。
可逆圧縮
可逆圧縮を、PNGファイルと比べてみます。 対象はPNGがよく使われるWebのUIアイコンとして、下記のPNGをもとにWebPに変換したものを比較します。
::
$ cwebp uikit.lossless.png -o uikit.lossless.webp -lossless
PNG | WebP |
---|---|
74 KB | 62 KB |
非可逆圧縮
非可逆圧縮をJEPGファイルと比べてみます。 対象はJPEGがよく使われる写真として、おなじみlennaさんのPNGをもとに、ImageMagickでJPEGに変換したものとWebPに変換したものを比較します。
::
$ cwebp lenna.lossless.png -o lenna.lossy.webp
$ convert lenna.lossless.png lenna.lossy.jpeg
JPEG | WebP |
---|---|
74 KB | 20 KB |
アニメーション
5つのJPEG画像をもとに、500msごとに切り替えるアニメーションをGIFとWebPで作って比較します。
::
$ webpmux -frame 1.webp +500+0+0+0 -frame 2.webp +500+0+0+0 -frame 3.webp +500+0+0+0 -frame 4.webp +500+0+0+0 -frame 5.webp +500+0+0+0 -o animation.webp
$ convert -delay 50 -loop 0 *.jpg animation.gif
GIF | WebP |
---|---|
262 KB | 55 KB |
残念ながら執筆時点では、WebPアニメーションを表示できるブラウザはないため、上記は表示されてないかもしれません。その場合はvwebpコマンドで表示できます。
::
$ vwebp animation.webp
GIFは色数が少ないため、写真を元にするとざらついた画質になりますが、WebPは比較して綺麗なアニメになっていることが分かると思います。
圧縮解凍の速度
WebPは圧縮率を上げるために、以下の追加コストがかかります。*4
- 圧縮: JPEG より 5~10 倍程度のコスト
- 解凍: JPEG より 1.3 倍程度のコスト
特に圧縮は大きな差に感じます。 しかし、画像は一般的に一度作られたら更新されることが少ないリソースであり、圧縮が必要なのは一回ですが、参照される機会が多いのが特徴です。 WebPは多少時間をかけて、じっくりと小さく圧縮することを選択しています。
CPU資源は、サーバ側はサービスの規模に応じて投資してコントロールできますし、クライアント側は最近はモバイル端末ですらそれなりの処理性能を持っています。 ネットワーク資源は、サーバとクライアント間で状況により変わり、サービス側の投資だけではコントロールしきれないという特徴があります。 したがって、圧縮/解凍にかかる時間よりも、ファイルを小さくすることを選択するほうが、現在のWebを取り巻く環境を考えると妥当な場合が多いと考えられます。
しかし、動的に画像を生成するようなサービスでは、従来のフォーマットと比べるなどの検証が必須になるでしょう。
*4 http://www.igvita.com/slides/2013/io-webp.pdf (P.13)
フォールバック
執筆時点では対応するブラウザは限られています。従ってWebP非対応のブラウザのためにはPNGなどへのフォールバックを検討する必要があります。 対応方法は主に以下があります。
- サーバ側で対応
- Accept ヘッダでの判別
- User-Agent ヘッダでの判別
- クライアント側対応
- JavaScript でのフォールバック
Accept での判別
クライアント・サーバ間での対応フォーマットの確認は、通常HTTP1.1のコンテントネゴシエーションという機能を用いて行います。 具体的には、WebPに対応したブラウザは、画像のリクエストのAcceptヘッダにimage/webpを付与することが求められており、サーバはこの値をもとにクライアントの対応を知ることができます。
手元のChrome29では、HTML中のimgタグのリクエストに以下のヘッダが確認できました。
この方法は、クライアントとサーバの間にキャッシュサーバが入る場合に注意が必要です。 同じURLから取得できるコンテンツがクライアントによって異なるので、キャッシュが共有されることで問題が起こる可能性があるためです。 これを防ぐためには、キャッシュに「Acceptヘッダによって、配信するソースが違う」ということを教えるためにVaryヘッダを付与します。 (この情報はGoogleのBotなどがコンテンツ内容を把握する上でも重要です。)
::
Vary: Accept
コンテントネゴシエーションをサーバに設定する例は、webp-detectが参考になります。
User-Agentでの判別
Chrome29は先のように、imgタグに対してimage/webpを送っていましたが、直接画像のURLにアクセスした場合は送っていません。(Canaryでは全ての画像のリクエストに送ります) またWebPに対応していなくても、全てを許容する意味の */* を送信するブラウザもあり、Acceptヘッダだけで判別するのは難しい場合があります。
そして、せっかく画像サイズを減らしているのに、Acceptヘッダに新しい情報を追加してしまっては、クライアントからサーバへ送信する情報を増やします。その理由から全てのブラウザが、今後Acceptヘッダを対応をするかどうかはまだ分かりません。 *3
こうしたブラウザ場合は、対応をUser-Agentから判断することもできます。 対応するブラウザは、http://caniuse.com/webp から確認できますが、より細かくバージョンを指定したい場合は、 Google Page Speed のソースが参考になります。
*3 HTTP/2.0では、HPACKというヘッダの送受信を効率化する方式が採用される見通しであり、ヘッダへの情報追加によるオーバーヘッドは、HTTP/1.1に比べて減らせる可能性があります。
Nginx のサンプル
以下は、ここまでの判別方法を元に、Nginxでブラウザの対応に応じて切り替える例です。 対応している場合はlogo.webpを、非対応の場合はlogo.pngをレスポンスしています。
1 2 3 4 5 6 7 8 9 10 11 |
location = /logo { if ($http_accept ~* "webp") { add_header Vary Accept; rewrite (.*) $1.webp last; } if ($http_user_agent ~* "(Chrome|Opera|Android|Android.*Chrome)") { add_header Vary User-Agent; rewrite (.*) $1.webp last; } rewrite (.*) $1.png last; } |
実際に以下のリンクで動作を確認できます。(対応ブラウザはWebP、非対応はPNG画像が返ってきます)
https://jxck.io/labs/webp/logo
HTMLごと切り替える
logo.webpとlogo.pngのURLを明示的に分けたい場合は、それらを含むHTMLをまるごと切り替える方が良い場合もあります。
1 2 3 4 5 6 7 8 9 |
// 対応ブラウザ用 <html> <img src="/logo.webp"> </html> // 非対応ブラウザ用 <html> <img src="/logo.png"> </html> |
この場合も、User-Agentによって切り替え、Varyヘッダを付与するとよいでしょう。 もしくは、以下のヘッダを付与することでキャッシュの共有を限定してしまう方法もあります。
::
Cache-Control: private
ただしこの方法では、例えばWebPに対応したクライアントで取得したlogo.webpのURLをtwitterなどで共有した場合、 WebPに対応しないクライアントでそれを開くと中身を見ることはできません。こうした状況を想定するならUser-Agentも併用する必要があるでしょう。
JavaScriptでフォールバックする方法
まず、Modernizr.jsには、ブラウザのWebPサポートを調べる機能があります。 これを用いて、読み込む画像をJavaScriptで動的に切り替えることができます。
1 2 3 4 5 |
if (Modernizr.webp) { // WebP を読み込む } else { // PNG を読み込む } |
また、WebPがWebMの1フレームに相当することを利用し、 videoタグでWebPを表示可能にする weppy.js というライブラリもあります。
これを用いると、WebMに対応したFireFoxでもWebPが表示できます。
1 2 |
<script src="weppy.js"></script> <img src="mandelbrot.webp" width="500"> |
より強力なライブラリとして、WebPのデコードをJSでやってしまうwebp.jsというライブラリもあります。 こちらは、なんとIE6以降でほぼ全てのブラウザに対応しています。
1 |
<script type="text/javascript" src="js/webpjs-0.0.2.min.js"></script> |
まとめ
画像を効率よく圧縮できれば帯域を節約でき、特にモバイル対応の場合は全体のパフォーマンスに大きく影響が出ます。 WebPはこの点を解消するために生まれ、圧縮率等に関しては一定の成果を出していると言えるでしょう。
しかし、まだ普及しきっていないため、フォールバックなど考慮すべき点もあります。特に、フォールバックのために施した対策のために、WebPで得られた以上のオーバーヘッドが生じては、パフォーマンスの観点からは台無しです。(全てWebP対応を考えた上での暫定策であれば別ですが)
また、圧縮効果が高いかどうか、不可逆でも画質を維持できるかどうかは、全てどういう画像をどういう用途で配布するかにかかります。パフォーマンスの向上も、今回解説したことを踏まえて、「推測するな測定せよ」を忘れずに実施してください。
Version
- libwebp 0.3.1
- ImageMagick 6.8.6-3
- Mac OSX 10.7.5
Photo License
- Lenna: http://www.cs.cmu.edu/~chuck/lennapg/lena_std.tif
- Smooth UI Kit: Creative Commons Attribution 3.0
- Photo Credit: Luz Adriana Villa A. via Compfight cc
- Photo Credit: pedrosimoes7 via Compfight cc
- Photo Credit: kevin dooley via Compfight cc
- Photo Credit: kevin dooley via Compfight cc
- Photo Credit: VinothChandar via Compfight cc
- Photo Credit: See-ming Lee 李思明 SML via Compfight cc