Webサービス上の画像変換とWebPの利用について

Mercari Advent Calendar 2020の14日目は、Developer Productivity EngineeringのNetworkチーム所属の@catatsuyがお送りします。

今回のエントリーでは画像変換とGoogle社が開発した画像フォーマットであるWebPに関して紹介します。

適切なサムネイル画像のサイズと画質

現在ではiPhoneのRetinaディスプレイのように、実際に画面に表示されるサイズよりも大きなサイズの画像を要求する端末が一般的になりました。 現在のスマートフォンでは画面に表示されるサイズの2倍以上のサイズ(端末によっては3倍に対応しているものもある)の画像を用意しなければ綺麗に表示されません。サムネイル画像の綺麗さはUXに直結しますし、サービスによっては画像の綺麗さがクリック率や売り上げに貢献するケースもあるでしょう。

しかし大きなサイズの画像を配信する場合、当然転送量もその分増大します。画像の転送量が大きければCDNなどの転送量がかかるだけでなく、サービスのレスポンスタイムに直結します。またスマートフォンの場合は月の通信量が決まっている人が多いため、容量の大きい画像を配信すれば「ギガが減る」と呼ばれる事態になります。転送量をできる限り減らすこともサービス提供者側にとって非常に重要です。

現在Web上で最も使われている画像フォーマットはJPEGだと思いますが、JPEGなどの画像では大きく2つの意識するべきパラメータがあります。それは

  • 画像サイズ
  • quality

の2つです。qualityについてはJPEGの場合0から100までが選択できますが、qualityを下げると大幅に画像が劣化してしまうので、あまり低い数値は指定できません。Google社はJPEGのqualityとして85をかつて推奨していました。私の主観ですが、JPEGのqualityは基本的には85を起点に調整するべきだと考えています。画像の劣化を考えると80より小さい数値にすることは難しいと思いますし、90以上だと容量が気になってきます。なので85がいいバランスになるサービスが多いと思いますが、ここはサービスの特性にもよるので必ず見比べてみて、容量と品質のバランスがいいものを選択してください。

実際に見比べてみると分かりますが、画像の綺麗さにおいてはqualityよりも画像サイズの方が重要です。十分な画像サイズで表示できるようにしてからqualityを調整するようにしてください。私の主観ですが、qualityはよく見ると違いが分かるという類いの物である一方で、サイズが小さい画像は見た瞬間に違いが分かります。

参考にサイズとqualityが違う画像を比較してみましょう。写真は私のPixel 4aを使って撮影した写真(JPEG)を使用し、ImageMagickで-stripオプションを付けて不要な情報を除去しています。画像のキャプションに以下の情報を載せていきます。

  • 画像フォーマット
  • 実サイズが1倍のサイズの画像は@1x、2倍の画像サイズの画像は@2x
  • 画像の容量
  • 指定したquality
JPEG, @1x, 66KB, quality: 90
JPEG, @1x, 46KB, quality: 80
JPEG, @1x, 36KB, quality: 70
JPEG, @2x, 265KB, quality: 90
JPEG, @2x, 190KB, quality: 80
JPEG, @2x, 150KB, quality: 70

またWebサービス上でさまざまな理由で意図せずに容量の大きいファイルを配信してしまうことがあります。メルカリでは画像を含めたほとんどの静的ファイルをFastly経由で配信しています。そこでメルカリではDatadogのFastly integrationを使ってオブジェクトサイズごとのオブジェクト数をグラフ化し、監視を行っています。fastly.object_size_1kには1KB未満のオブジェクトのオブジェクト数、fastly.object_size_10kには1KBから10KBのオブジェクト数、というようにそれぞれをDatadogのMetricsとして取得できるようになっています。詳しくはドキュメントを参照してください。

Fastly | Datadog

実際の事例について弊社の @cubicdaiya の発表資料でも紹介されているので参照してください。

Handling a tremendous amount of images with Fastly / Yamagoya Traverse 2020 – Speaker Deck

また弊社は現在ではImageFluxFastly Image OptimizerなどのSaaSを使って画像変換を行っています。今回のエントリーはそういった画像変換のSaaSを利用する上での注意点をサービス利用者視点で紹介できればと思います。

次節からGoogleが開発した画像フォーマットであるWebPの紹介をします。適切に使えば非常に大きな効果を得られるので是非活用して欲しい技術です。

WebPについて

WebPはGoogle社が開発した画像フォーマットです。元々はGoogle社が開発しているGoogle ChromeやAndroidだけで利用できる画像フォーマットでしたが、先日リリースされたApple社のSafari 14でも表示ができるようになりました。Firefoxでも利用できますし、モダンブラウザに限定すれば使用できる環境が広く普及しつつあります。

A new image format for the Web | WebP | Google Developers

WebPには複数の仕様があります。かなり雑な説明ですが、WebPにはJPEG相当のWebP lossy、PNG相当のWebP lossless、アニメーションGIF・APNG相当のanimated WebPなどがあります。それぞれ別の仕様ですが、WebPとしてまとめて開発されています。この中でWebP lossyが最も古く、おそらく最も使われているのではないでしょうか。一般的にWebPと言った場合はWebP lossyを指すことが多いと思います。本記事では断りがない場合WebP lossyのことをWebPと呼ぶことにします。

先程紹介したJPEG画像をWebP画像に変換して比較してみます。WebPは後ほど紹介するcwebpを用いてオリジナルのJPEG画像を変換しています。

注:WebP画像を直接参照しているため、WebPに対応したブラウザでしか確認できません。以下他にもWebP画像を直接参照している箇所があります。

JPEG, @2x, 220KB, quality: 85
WebP, @2x, 158K, quality: 75
WebP, @2x, 137KB, quality: 60
WebP, @2x, 114KB, quality: 40

メルカリなどのWebP活用事例

メルカリでは以前よりAndroid・iOSアプリを中心にWebPを広く使っています。それについては以前にも紹介されているのでこちらの記事を参照してください。

WebPをうまく利用することで綺麗で容量の小さな画像を配信できます。私が非常に参考にしているのがYouTubeのサムネイル画像です。

2020/12時点で、YouTubeのサムネイル画像は以下のような動きをします。

  • WebP非対応ブラウザならサムネイル画像としてJPEG画像を返す
  • WebP対応ブラウザならサムネイル画像としてWebP画像を返す
  • animated WebP対応ブラウザならサムネイル画像にマウスオーバーしたときにanimated WebPの画像を返す

YouTubeのWebPのサムネイル画像を拡大してよく見るとqualityは低めにしてあるように思えます。しかしサイズが十分大きいので、サムネイル画像としては容量が小さいのに綺麗な画像を返せています。

WebPへの変換方法

JPEGなどの画像を自分のマシン上でWebPに変換したい場合はcwebpとdwebpコマンドをインストールする必要があります。

Getting Started | WebP | Google Developers

MacのHomebrewを使用している場合は以下のコマンドでインストールできます。

brew install webp

依存するライブラリもインストールする必要があるので注意してください。またアニメーションGIFをanimated WebPに変換するgif2webpはcwebpなどよりも依存するライブラリが多いためインストールする際は注意してください。

cwebpがEncoderでJPEG画像などをWebP画像に変換するコマンドで、dwebpがDecoderでWebP画像をJPEG画像など他の形式に変換するコマンドです。-qでクオリティが指定できたりと、さまざまな機能があります。詳しくはドキュメントを参照してください。

WebPの特徴と注意点

そろそろWebPを使いたくなってきたと思うのですが、当然WebPも万能ではありません。特徴と注意点を知った上で使う必要があります。

例えばJPEGではprogressiveやinterlacedを利用すれば、最初は粗い画像を表示して少しずつ最終的な画像を表示することができましたが、WebPでは利用できません。WebPではJPEGにあった機能が一部使えないこともあるので、こういった機能を活用していたサービスでの導入を考えている場合は事前に確認した方が良いでしょう。

ここではその他にもWebPを実際にサービスに組み込む際の特徴や注意点を紹介していきたいと思います。

JPEGとWebPのqualityについて

WebPの話をする前にJPEGの特徴について簡単に紹介します。

JPEGではqualityを下げて圧縮率を上げていくと、モスキートノイズやブロックノイズといった画像の乱れが起こります。それがJPEGではqualityを大きく下げることができない理由の1つです。

ブロックノイズ
ブロックノイズ Wikipediaより引用
モスキートノイズ
モスキートノイズ Wikipediaより引用

JPEGのqualityと単純比較はできませんが、WebPはそういったノイズは起こりにくくなっているため、JPEGよりかなりqualityに指定する数値を落とすことができます。コマンドラインでWebPに変換できるcwebpのデフォルトのqualityは75です。

qualityの低いJPEG画像をqualityの高いWebP画像に変換すると容量が大きくなることがあるので、WebPの画像は十分大きなサイズでqualityは低めにするべきです。画像によってはデフォルトの75よりももっと低い数値をqualityに指定することもできると思うので、サービスの特性に合わせて調整してみてください。

また公式のFAQにもさまざまな情報が載っているのでWebPを活用したい場合は確認するのがお勧めです。

Frequently Asked Questions | WebP | Google Developers

WebPで気をつけるべき画像

WebPはqualityの数値を低めにするべきということを紹介しました。しかしqualityが低めのWebP画像にすることで印象が変わってしまう画像があります。どういった画像で劣化が大きいか紹介します。

例えば以下のようなテキスト画像をPNGで用意します。分かりやすいようにここで紹介する画像はブラウザ上で拡大しています。

PNG, 13KB

この画像をWebPに変換した画像が以下です。分かりやすいようにqualityは30でブラウザ上で拡大表示しています。

WebP, 2.6KB, quality: 30

よく見ると文字の周囲がぼやけているのが分かると思います。

次はテキスト画像をモザイク化してドット絵のようにした以下の画像を変換してみます。

PNG, 3.2KB

先程と同様にqualityは30でブラウザ上で拡大表示しています。

WebP, 718B, quality: 30

ドット絵らしさが失われていることが分かると思います。

WebPのデフォルトのパラメータでは写真などを効率よく圧縮するためにディザリング (dithering) と呼ばれる手法を用いて、画像を全体的にぼやかしたように変換されます。写真の場合はこのようにぼやかしても画像の見た目の変化は小さく、効率よく容量を減らすことができますが、テキストやドット絵のような画像だと紹介したように劣化したように見えることがあります。

実はcwebpコマンドには-presetというオプションがあります。現在ではdefault, photo, picture, drawing, icon, textが選べます。今回紹介したケースではそれぞれtexticonを選択することでディザリングを無効にするなど、パラメータを調整した上で変換を行えます。なので今回紹介したケースはWebP自体の特徴ではなく、cwebpのデフォルトのパラメータの特徴です。しかし実際のサービス運用ではパラメータを調整するのは難しいので、WebPを使用する際の注意点として知っておくと良いと私は考えています。

実際にpresetにそれぞれtext, iconを指定した画像が以下の物です。

WebP, 2.9KB, quality: 30, preset: text
WebP, 716B, quality: 30, preset: icon

こういった画像はどう扱えばいいのでしょうか? これについてはサービスの特性によるとしか言えないのですが、そもそもテキスト画像やドット絵は容量が小さなファイルが多いので無理に変換しなくても良いケースが多いです。WebPの効果が大きいところだけでWebPを使っていくのが良いWebPの使い方だと私は考えています。

TLS 1.2only化とSafariのWebP対応

そんなWebPですが、最近WebPが普及する上で障害になっていたことが解決しつつあります。

私見ですが、WebP普及においてこれまで問題になっていたのは以下の2点だと思います。

  • iPhoneのSafariで使えない
  • Android 4.3以下だとWebPの画像の表示が崩れてしまうので実質的に使用できない

1つ1つ説明していきます。

スマートフォンが普及し、iPhoneからのアクセスは無視できないサービスが多いと思います。そのような状況だといくらWebPの効果が大きいとはいえ、iPhoneで効果がない施策は打ちづらいでしょう。

またAndroid 4.3以下では後ほど紹介するWebP対応ブラウザの判定方法を使うと、WebPを使用できるように見えますが、実際にWebP画像を表示させると表示が崩れてしまいます。

そこでメルカリではAndroid 4.3以下の端末にはWebP画像のURLを返さないようにしています。以下のエントリーでも紹介されていて、既に導入しているサービスではそのように対応しているケースがほとんどではないでしょうか。

WebPでモバイルアプリの通信量を劇的に削減する – クックパッド開発者ブログ

しかしこれらの問題が現在解決しつつあり、WebPの対応状況も変わってきています。それについて紹介します。

iPhoneについてはSafari 14のリリースにより、新しいSafariならWebPを表示できるようになりました。後ほど紹介するWebP対応ブラウザの判定方法を使用していれば、アプリケーションの変更なしで対応できます。

Safari 14 Release Notes | Apple Developer Documentation

また昨今のセキュリティ向上の流れから、TLS 1.2未満のTLSバージョンを無効にするサービスが増えていますが、Android 4系のWebViewや内蔵ブラウザからはTLS 1.2が無効になっています。TLS 1.2未満のサポートを落とすことでAndroid 4系からのリクエストは受信できなくなります。

TLS 1.2未満のTLSバージョンはクレジットカード業界のセキュリティ基準であるPCI DSSでは、2018年6月末までしかサポートが許されていません。なので既に決済などに関わるサービスのほとんどはAndroid 4系で通信が行えなくなっています。PCI DSSに準拠する必要がないサービスでもTLS 1.2未満のサポートを落とすサービスが増えています。例を挙げれば github.com や twitter.com はすでにサポートしていません。また弊社サービスの www.mercari.com も既にTLS 1.2未満のサポートはしていません。

以上のように最初に紹介した問題点は解消してきているので、導入をためらっていたサービスも安心して導入できる時代になって来ているのではないでしょうか。

WebP対応ブラウザの判定方法

WebPを導入する際に重要なことはWebPが扱えないクライアントが存在するので、WebPが使えるクライアントにだけWebP画像を表示する仕組みです。ここではWebPが使えるか判定する方法をいくつか紹介します。

おそらく最も一般的なのがAcceptヘッダーを使う方法だと思います。ブラウザの場合、クライアントが理解できるコンテンツタイプをAcceptヘッダーに入れた上でリクエストを送ってくれます。私が現在使用しているMac版のChrome 87ではAcceptヘッダーは以下のような内容です。

text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9

この中にimage/webpが含まれるため、サーバー側はWebPを使用できるクライアントだと判定できます。

これだけ聞けば、Acceptヘッダーですべて判定できるように思いますが、注意しなければならないのは、上で例示したAcceptヘッダーはajaxではないブラウザリクエストにのみ含まれる点です。ajaxの場合、Acceptヘッダーには*/*が指定されるだけで、WebPに対応しているクライアントかどうか判定できません。ajaxで画像URLが含まれるJSON APIを叩いて、その画像を表示するという実装はとても一般的ですが、そのようなリクエストではAcceptヘッダーを使って判定できません。

なのでここが非常に厄介な点なのですが、Acceptヘッダーを使ってWebPに対応しているか判定する場合、ajaxではないリクエストのAcceptヘッダーにimage/webpが含まれている場合はCookieなどを用いて、そのクライアントがWebPに対応していることを保持しておき、ajax経由のリクエストでもWebPが使用できるか判定できるようにする必要があります。この実装の難点はアプリケーション側の変更が複数箇所にまたがるので、手間がかかってしまう点でしょう。

ここまでアプリケーションで判定する方法を紹介しましたが、画像配信を行うCDN側で配信する画像形式を判断することもできます。

ajaxのリクエストのAcceptヘッダーにはimage/webpが含まれてないことを紹介しましたが、画像配信サーバーへのリクエストのAcceptヘッダーには当然image/webpが含まれています。画像配信サーバー側でAcceptヘッダーの内容によって、同じURLでもWebP対応端末にはWebPを、非対応端末にはJPEGを返す実装をすれば可能です。こう書くと難しく聞こえるかもしれませんが、既に紹介した画像変換サービスのImageFluxやFastly Image Optimizerではオプションを有効にするだけでこの挙動を実現できます。詳しくはImageFluxのドキュメントFastly Image Optimizer APIのドキュメントを参照してください。

またHTMLタグのpictureタグを使用してブラウザに使用する画像を判断してもらう方法があります。以下のようなHTMLです。

<picture>
  <source type="image/webp" srcset="images/butterfly.webp">
  <img src="images/butterfly.jpg" alt="a butterfly">
</picture>

Built-in Browser Support for Responsive Images – HTML5 Rocks

pictureタグに対応していない古いブラウザの場合、内部のimgタグが呼び出されるだけなのでこのコードは安心して利用ができます。pictureタグに対応したブラウザはsourceタグの中から自身に適切な画像を選択し、最適な物がなければimgタグの画像を表示します。type以外にもディスプレイサイズによってURLを変更することもできるので、ディスプレイサイズが大きいディスプレイにはサイズが大きい画像を返して、小さいディスプレイを使用している場合はサイズが小さい画像を返すことで最適な画像を返すという使い方もできます。詳しくはMDNを参照してください。

なお上記のように配信する画像を変える方法は以下のMercari Gears Lecture Seriesのエピソードでも詳しく解説されていますので、ぜひご覧ください。

Ethan Marcotteの動画REsponsive Web Design - The Next Decadeのサムネイル

前述の通り、WebP以外にディスプレイサイズを使用して最適な画像を選択できるようになるので、適切に指定すれば強力な最適化を行えます。非常に便利ですが、難点としてはアプリケーション上のテンプレートをそれなりに考えながら書き換える必要があるので工数がかかる点でしょう。ただし効果が大きい箇所に関しては工数をかけてでも対応する価値はあると思います。

最後に紹介するのはJavaScriptで実際に画像として書き込んでみる手法です。以下のように1px四方のWebP画像をJavaScript経由で書き込んでみて、画像として認識されるかを確認します。もし画像として認識されれば画像サイズが取得できるはずです。

var img = new Image();
img.src = 'data:image/webp;base64,UklGRiIAAABXRUJQVlA4IBYAAAAwAQCdASoBAAEADsD+JaQAA3AAAAAA';
console.log(img.witdh); // 1

前述の通り、WebPにはWebP lossyの他に、WebP losslessやanimated WebPもあります。ここまで紹介した手法では例えば「WebP lossyは扱えるが、WebP losslessは扱えないクライアント」を判定できません。特にanimated WebPは各種ブラウザが対応し始めたのは比較的最近です。WebPが扱えるからといってanimated WebPを扱えると断定するのは危険です。WebPの対応状況を細かく確認したい場合はこの方法が良いでしょう。詳しくは公式のFAQを参照してください。

最後に

ここまで画像変換とWebPの活用方法について紹介をしてきました。JPEG画像をWebP画像へ変換するだけならcwebpコマンドでできるので、SaaSを使わずに自前で変換サーバーを作れると考える人がいるかもしれません。

しかし実サービスだと例えば色空間を保存しているICCプロファイルが壊れているJPEGファイルがあるなど、画像ファイルとしては本来壊れているファイルも一般的に使われていることがあります。そのような画像でも色ができる限り変わらないように変換する必要があったりと、実際に画像変換サーバーを運用しようとするとさまざまな課題があります。社内には画像変換サーバを開発運用していたメンバーもいますが、実際に多くの課題に直面していた経験がありました。具体的な課題については以下の資料が参考になります。

画像処理サービスを作る際の落とし穴をImageFluxではいかにして超えてきたか / ImageFlux meetup #4 (5) – Speaker Deck

弊社が画像変換のためにSaaSを使う理由の1つはそういったSaaSの持つ豊富な経験・知識を利用したいからです。

今回紹介したJPEGやWebPの特徴を理解した上で、SaaSをうまく活用して最適な画像を配信することが、サービスを利用しているお客さまのUXを向上させる近道だと私は考えています。

メルカリではさまざまな技術やSaaSを活用してお客さまに最高のUXを届けたいソフトウェアエンジニアを募集しています。