世界を動かす技術を、日本語で。

なぜ小さなJPEG画像はChromeで異なって見えるのか

概要

  • ChromeのJPEGデコード最適化が アイコン描画の違い の原因
  • 部分IDCTスケーリング による効率的な縮小処理
  • 高周波情報 が縮小時に主に失われる特徴
  • Chromeは Skiaとlibjpeg-turbo で処理を実施
  • アイコンやロゴには JPEG非推奨 という教訓

ChromeのJPEGデコード最適化とアイコン描画の違い

  • ChromeとFirefox で同じロゴ画像を表示した際の見た目の違い

    • Chromeでは画像が 太く見える 現象
    • SVG画像に差し替えると問題が解消
  • 画像縮小時の一般的な処理

    • JPEGを完全にデコードし、縮小する直感的な方法
    • 例:2000×2000のJPEGを20×20で表示する場合、 無駄なメモリ消費
    • 元画像の多くの情報が縮小時に失われる
  • 縮小時に失われる情報の特徴

    • 主に 高周波情報 (細かいディテール)が消える
    • 例:木の葉や樹皮の細部は縮小すると単純な色の塊になる
  • JPEGの画像データ保存方法

    • 画像を 8×8ブロック に分割し、周波数領域に変換(DCT)
    • 低周波成分 =単色、 高周波成分 =チェッカーボード状
    • 各パターンの量を 係数(coefficient) として保存
  • 1/8スケールでのJPEG描画

    • 8×8ブロックが縮小後は 1ピクセル になる
    • 低周波成分 のみ必要、高周波成分は省略可能
    • 係数の一部のみ使うことで 高速・省メモリ にデコード可能
    • この手法は partial IDCT scaling (部分IDCTスケーリング)と呼ばれる

Chrome/SkiaのJPEG縮小処理の仕組み

  • Chromeは画像デコード・描画をSkiaに委任

    • Skiaは libjpeg-turbo を利用
    • 部分IDCTスケーリング により低周波成分だけをデコード
    • 8分の1など、 8の分数単位 で縮小可能
    • さらに必要な場合は従来のダウンサンプリングも実施
  • 現象の理由

    • 小さいサイズで表示時、 定数成分のみ が残り、エッジの柔らかさやグラデーションが失われる
    • そのためChromeでは画像が 太く・単純化 されて見える

JPEG最適化の教訓と推奨

  • JPEGは写真向けフォーマット
    • Joint Photographic Experts Group の名が示す通り、写真に最適化

    • アイコンやロゴなど 細部や輪郭重視の用途には不向き

    • SVGやPNGなど 非圧縮・ベクター形式 推奨

    • Chromeの最適化による描画差異の発生理由

    • JPEGの縮小最適化手法の技術的背景

    • 適切な画像フォーマット選択の重要性

Hackerたちの意見

じゃあ、Firefoxはフルレンダリングしてからスケーリングしてるの?それとも別の方法で部分的にレンダリングしてるの?片方の話しかしてないよね。

Firefoxは画像をフル解像度のバッファにデコードしてからダウンスケールするわけじゃなくて、ストリーミングデコードをして、その場でダウンスケーリングを適用してるんだ。これを「デコード中のダウンスケール」って呼んでるみたい。[0] https://bugzilla.mozilla.org/show_bug.cgi?id=1045926 でも、8x8ブロックの部分デコードはしてないと思う。

ああ!そこにたどり着くまで結構時間がかかった。Firefoxのパイプラインを調べて、別の投稿を作ろうかな。その間に、Firefoxの内部について知ってる人がいたら、ぜひ教えてほしい!

ChromeとFirefoxは、全然違うスケーリングアルゴリズムを使ってるから、これが差に大きく影響してると思う。Chromeは全体的にぼやけてるけど、Firefoxはシャープだけど少しリングアーティファクトが目立つかな。個人的にはFirefoxの方が好きだな。

私もFirefoxの見た目が好きだよ!これの根本的な原因について、あなたの意見を聞いてみたいな。私は画像レンダリングの専門家じゃないけど、DCTビジュアライザーで画像のエッジやカーブを調べたとき、すべてAC係数にあったのを覚えてる。それがこの投稿の結論に至った理由なんだ。

画像の質に関する客観的な指標の多くは、リングよりもぼやけを好むけど、主観的な測定は逆の傾向があることに気づいたよ。

メモリが少ないときだけ部分的なスケーリングをするっていうのは面白いかもね。

あはは、ChromeがRAMをめっちゃ食うことを考えると、20MBはあんまり勝ちじゃないよね。で、バグを再現しようとしたら、結構難しかった!写真ではほぼ見えないし、アイコンも全部に影響があったわけじゃない。だから、ちゃんと機能してるんだよね。アイコンにjpgを使ったのはちょっと失敗だったかな、あはは。

メモリの問題じゃなくて速度の問題だよ。メモリは遅いから(CPUキャッシュと比べると)、その差は大きいんだ。

Firefoxで低解像度でデコードする作業はここで行われてるよ: https://bugzilla.mozilla.org/show_bug.cgi?id=2033250

ありがとう、もっと効率的だって知ってたけど、ベンチマークの数字を見るとこの「トリック」がさらにありがたく感じるね!

わあ、これで特定のベンダーからの画像で遭遇した問題が説明できる。IDCTスケーリングを使った理由はこれだね:いくつかのエンコーダー(実際に流通しているJPEGのかなりの割合)が、トレーリングMCUの未使用行/列にゼロやゴミを残すんだ。フルサイズでデコードするとそのサンプルは切り取られるけど、IDCTスケーリングでは隣接する出力ピクセルに混ざって、アーティファクトが見えるようになる。詳しくは https://crbug.com/890745 と https://github.com/libjpeg-turbo/libjpeg-turbo/issues/297 を見てみて。私たちの場合は、エッジを切り取るだけで済んだから、そのトレードオフは価値があったよ。

Hacker Newsで議論の続きを見る