Web開発
小さなJPEG画像がChromeで異なる表示になる理由
Why Tiny JPEGs Look Different in Chrome (guillaumetech.github.io)
要約
Chromeブラウザで小さなJPEG画像が異なる表示になる現象は、レンダリングバグではなく、JPEGデコーディングにおける巧妙な最適化によるものでした。JPEGは高周波成分を圧縮するため、極端に縮小されるとこれらの情報が失われ、結果として画像が太く見えることがあります。この最適化は、特にアイコンなどの小さな画像には不向きです。
全文翻訳
2026年8月3日月曜日
小さなJPEG画像がChromeで異なる表示になる理由
レンダリングバグのように見えたものは、Chromeにおける巧妙なJPEGデコーディング最適化であることが判明しました。
このアイコンは同僚のコンピューターではより良く見えます。
しばらく前、同僚のコンピューターでチャットしているときに、あるロゴが自分のコンピューターで見るものと全く同じではないことに気づきました。彼の方ではより細く、元の画像により忠実に見えました。
それは15pxでレンダリングされていました。以下は拡大したバージョンです。注意:これは元の画像ではありません。しばらく前のことだったので、問題を実証するために新しい画像を作成しました。
左がFirefox、右がChromeです。目を細めて見るか、少し離れて見ると、Chromeの方が太く見えます。
少し奇妙ですが、SVGに画像を置き換えることで解決しました。それでも、なぜそもそもこのようにレンダリングされるのか、興味がありました。
調査したところ、Chromeが小さなスケールでJPEGをレンダリングする際に使用する、気の利いた最適化を見つけました。
画像を小さくスケーリングするのは無駄が多い
JPEGから小さな画像をレンダリングする直感的な方法は、メモリ内で完全に展開してから縮小することです。しかし、それは常に効率的ではありません。
例えば、2000×2000のJPEGを20×20で表示する必要があると想像してください。展開後、画像は最終結果よりもはるかに多くのメモリを消費します。
フル画像のビットマップは約12MBを使用しますが、最終的な20×20の画像は約1.2KBしか必要としません。
大きなバージョンで失われる情報のほとんどは、縮小時に失われます。
縮小時に失われる情報は何でしょうか?
興味深い洞察は、失われる情報がランダムではないということです。
画像を大幅に縮小すると、消える情報は主に高周波の詳細です。
これは直感的に簡単に理解できます。
たくさんの葉と粗い樹皮を持つ木を考えてみてください。それらの細かいディテールはピクセルごとに急速に変化するため、高周波情報と見なされます。
その木を20×10のような非常に小さなサイズに縮小すると、葉の部分は緑の塊になり、幹の部分は茶色の棒のようになります。
縮小されたバージョンは、細かいディテール、つまり高周波情報を捨てています。
木が縮小される様子を示すイラスト
高周波情報の一部は、ディテールが混ざり合うため、ある程度は残ります。
JPEGは画像データをどのように保存するか
この説明では、専門用語や数学は控えめにしますが、さらに深く掘り下げたい場合に役立ついくつかの技術用語に言及します。
また、完全なJPEG変換の大部分はスキップします。ここでは必要ないためです。
JPEG圧縮中、画像は8×8ピクセルのブロックに分割され、周波数領域に変換されます。
この操作はDCT(離散コサイン変換)と呼ばれます。
8×8ブロックでは、最も低い周波数は均一な色です。厳密には、何も変化しないため、実際には周波数ではありません。これは定数成分です。
反対に、最も高い周波数は市松模様のように見え、値が可能な限り変化します。
その中間のすべては、周波数領域の残りの部分を表します。
これらは基底関数と呼ばれます。
基底関数:左上には均一な色が見え、右下には市松模様が見えます。
したがって、8×8ブロックを周波数領域に変換することは、基本的に「このブロックにはどのパターンがどの程度存在するか?」と尋ねるようなものです。
それらの量が係数と呼ばれます。
JPEG圧縮には、それらの係数を効率的に保存するためのさらにいくつかのステップがありますが、そこで非可逆圧縮が発生します。
しかし、その部分は私たちが議論していることには重要ではありません。
まとめ:JPEGを1/8スケールでレンダリングする
ここで、画像を8分の1の比率で縮小したいとします。
先ほど言及した8×8ブロックは、縮小された画像では1ピクセルで表されるようになります。
そのサイズでは、画像は主に低周波情報しか必要としません。なぜなら、木の例のように、高周波のディテールは縮小時にほとんど失われるからです。
したがって、JPEG全体をデコードする代わりに、高周波部分の係数をスキップし、画像の粗いバージョンに必要なものだけを使用できます。
これにより、元の画像を完全に展開する前に、縮小された結果が得られます。
デコードされた画像はスペースを取りませんし、係数の大部分をスキップするため、展開も高速です。
これは、分母が8の分数である限り、他の比率にも拡張できます。
この技術的な名称は、部分IDCTスケーリング*です。
jpegclub.org(これを少し読むと、この技術が画像を拡大するためにも使用できることがわかります!)。
*逆離散コサイン変換:周波数領域を画像領域に戻すこと。
Chromeがどのように関係するか
Chromeは画像デコードとレンダリングをSkiaに委任します。
JPEGの場合、Skiaはlibjpeg-turboを使用しており、これは部分IDCTスケーリングを実装しています。
これにより、ターゲットサイズが十分に小さい場合に、低周波データのみをデコードできます。
つまり、Chrome/Skiaは常に完全な画像をデコードしてからスケーリングしているわけではありません。
8の倍数を持つ最も近い分数(ここでは1/8)を計算し、そのスケールで画像をデコードします。
その後、より伝統的なダウンサンプリングアルゴリズムを使用して、目的のサイズに達するまでさらに画像をスケーリングします。
だからこそ、私のマシンでは画像が太く見えたのです。
非常に小さくレンダリングされたため、部分IDCTスケーリングを使用して1/8スケールでデコードされました。
したがって、周波数表現から残ったデータは定数成分のみであり、エッジのぼかしやグラデーションは使用されませんでした。
実際、ここでの教訓は、アイコンなどにはJPEGを使用すべきではないということです。
このフォーマットとその最適化は、私たちの写真の知覚を中心に設計されています。
結局のところ、名前にもあります:Joint Photographic Experts Group(共同写真専門家グループ)です。