JA EN
体系画像圧縮
·無料·10分で読めます

PNGはなぜ劣化しないのか — 予測フィルタとDeflate

1ビットも変えずに縮めるとは、どういうことか。すべてのファイルを縮める圧縮器が存在しない理由から始めて、PNGの5つの行フィルタが「予測して差分だけ残す」仕組み、Deflate(LZ77+ハフマン)、そして写真が苦手でスクリーンショットが得意な理由まで。最後にJPEGとの使い分けを判断基準の形で置きます。

対象imageタスクcompression

同じ画像、違う結末

UIのスクリーンショットを1枚、JPEGとPNGで保存してみます。JPEGは小さいがボタンの文字の縁がざらつく。PNGは少し大きいが、拡大しても保存前と1ビットも違わない

前記事のJPEGは「並べ替えて後ろから捨てる」戦略でした。PNGは何も捨てません。それでいて元のRGB配列より小さくなる。捨てずに縮めるとは何をすることなのか——これがこの記事の主題です。

可逆圧縮とは何か、そして何ができないか

可逆圧縮の定義は単純です。圧縮して展開すると、元のバイト列が完全に一致する。近い値ではなく、同一。

ここで一度立ち止まる価値があります。すべての入力を短くする圧縮器は、原理的に存在しません。理由は鳩の巣原理です。nn ビットの入力は 2n2^n 通りありますが、nn ビット未満の出力は全部合わせても 2n12^n - 1 通りしかない。異なる入力を異なる出力に写す(=展開できる)以上、必ずどこかで衝突するか、一部の入力は逆に長くなります

つまり可逆圧縮とは「情報を減らす技術」ではなく、「実際に来るデータの偏りに賭ける技術」です。画像ファイルには強烈な偏りがある——隣り合う画素はよく似ている。PNGはこの1点だけを賭けの対象にしています。

PNGは2段構え

PNGの符号化は、大きく2工程しかありません。

  1. 行フィルタ — 各行の画素を「左や上から予測した値との差」に置き換える
  2. Deflate — 出てきたバイト列を LZ77 とハフマン符号で詰める

工程1は圧縮ではありません。バイト数は1ビットも減らないどころか、行あたり1バイト増えます。ではなぜやるのか。工程2が縮めやすい形に、データを作り替えているからです。JPEGにおけるDCTと立場は同じで、「捨てる準備」ではなく「詰める準備」をしています。

5つの行フィルタ

フィルタは各行の先頭1バイトで指定され、行ごとに別のものを選べます。いま符号化したい画素のバイトを xx、その左を aa、真上を bb、左上を cc とすると、5種類はこうなります。

None  :xSub  :xaUp  :xbAverage  :xa+b2Paeth  :xPaeth(a,b,c)\begin{aligned} \text{None}\;&: x \\ \text{Sub}\;&: x - a \\ \text{Up}\;&: x - b \\ \text{Average}\;&: x - \left\lfloor \tfrac{a+b}{2} \right\rfloor \\ \text{Paeth}\;&: x - \mathrm{Paeth}(a,b,c) \end{aligned}
(1)

記号を日常語に置き換えると、xx は「いま書こうとしている画素の値」、aabbcc は「すでに書き終えた左・真上・左上の画素の値」です。どの行の引き算も「近所から予想が付く分を差し引く」という意味で、\lfloor \cdot \rfloor は「小数を切り捨てて整数にする」だけの記号。ファイルに残るのは画素そのものではなく、予想の外れ幅のほうです。

この式が言っているのは要するに、「近所の値からこれくらいだろうと予測して、外れた分だけを書く」ということです。Sub は左の画素、Up は真上の画素、Average はその平均を予測値として使います。計算はすべて 256 を法とする足し算・引き算なので、復号側は予測値を足し戻すだけで完全に元へ戻ります。ここに近似は一切ありません。

Paeth だけ少し賢い作りです。p=a+bcp = a + b - c を計算し、a,b,ca, b, c のうち ppいちばん近いものを予測値として選びます。左上の cc を「基準点」とみなして、横方向の変化と縦方向の変化のどちらが効いているかを推測する仕掛けで、斜めのグラデーションや輪郭に強い。実際、多くのエンコーダで最も選ばれるフィルタです。

エンコーダはどう選ぶのか。標準的な実装(libpng系)は行ごとに5種類すべてを試し、フィルタ後のバイトを符号付きとみなした絶対値の合計が最小になるものを採る、というヒューリスティックを使います。0に近い値が並ぶほど後段が効く、という経験則をそのまま数値化したものです。

Deflate: LZ77とハフマン

工程2の Deflate(zip や gzip と同じもの)も2段構えです。

LZ77 は「前に出てきたのと同じ並び」を探し、その中身の代わりに (何バイト前から、何バイト分) という参照に置き換えます。PNGが使う設定では、過去32KBまでを窓として遡り、3バイト以上の一致を参照にできます。ベタ塗りの領域を Up フィルタに通すと 0 が延々と並ぶので、LZ77はそこで巨大な一致を見つけられます。

ハフマン符号は、残ったリテラルや参照の記号に、出現しやすいものほど短いビット列を割り当てます。これがエントロピー符号化そのもので、「よく出る記号を短く」という原理はモールス信号と変わりません。Deflateには固定テーブル方式と、そのブロック用のテーブルをストリームに埋め込む動的方式があります。

写真が苦手で、スクリーンショットが得意な理由

同じ仕組みなのに、素材によって結果が桁で変わります。理由はフィルタ後の残差の分布にあります。

スクリーンショット・図表・ロゴ。ベタ塗りの面では、左の画素と自分は完全に同じ値です。Sub フィルタを通せば残差はぴったり 0。行が丸ごと同じなら Up フィルタで行全体が 0 になります。0 の連続は LZ77 が一撃で参照に潰し、残った記号もハフマンで最短ビットになる。予測が完全に当たる素材なので、10分の1以下まで縮むことも珍しくありません。

写真。滑らかに見えるグラデーションでも、実際にはセンサノイズが乗っていて、隣の画素との差は 0 ではなく −3, +1, +2, −4 … と細かく散らばります。予測はほぼ当たるが、ぴったりは当たらない。残差は0付近に集中するのでランダムよりはずっとマシですが、完全な0の連続は生まれないためLZ77がほとんど効かず、ハフマンで稼げる分しか縮みません。だから写真のPNGは、同じ見た目のJPEGの5倍から10倍のサイズになります。

この差は品質の問題ではなく、素材の性質の問題です。フィルタとDeflateがどれだけ賢くても、センサノイズは予測できない——つまり情報理論でいうエントロピーが高く、可逆である限り誰にも縮められません。

FIG 1これはJPEG側の挙動。品質を下げれば下げるほど小さくなるが、元へは戻らない。PNGにはこのつまみがそもそも存在しない——縮み方は素材の予測しやすさだけで決まる

使い分けの判断基準

迷ったときは、この順で問うのが早いです。

1. 元に戻す必要があるか。 マスター素材、編集途中のファイル、テクスチャの元データなら可逆一択です。JPEGは開いて保存し直すたびに劣化が累積します。

2. 透明が要るか。 JPEGにはアルファチャンネルがありません。透過が必要なら PNG(またはWebP/AVIF)です。PNGのアルファは乗算済みではない「ストレートアルファ」で、8ビットの中間透明を素直に表現できます。

3. 素材はどちらの性質か。 連続階調(写真・グラデーション主体)なら非可逆が圧倒的に有利。少色でエッジが立っている(スクリーンショット・UI・図表・文字)なら可逆が有利で、しかもJPEGでは文字が滲みます。

4. ベクタで済まないか。 アイコンやロゴは SVG が第一候補です。解像度に依存せず、たいていPNGより小さくなります。ラスタ化が必要なときだけPNGを出します。

WebP と AVIF も選択肢に入ります。WebP は可逆・非可逆の両モードを持ち、可逆モードは独自の予測器と色変換を備えるためPNGより小さくなることが多い(画像によります)。AVIF は動画コーデックAV1のイントラ符号化を静止画に転用したもので、非可逆での効率が持ち味です。どちらも主要ブラウザで扱えますが、古い環境やメール、外部ツールへの受け渡しではPNG/JPEGのほうが確実です。実務では <picture> 要素で新形式を先に並べ、PNG/JPEGを最後の受け皿に置く形が定番です。

現場ではこう使う

Webやアプリで画像を出す人が、PNGについて実際に判断するのは次の点です。

書き出しの形式をルール化する。 「写真・サムネイル → JPEG(またはWebP/AVIF)」「UIスクリーンショット・図表・ロゴのラスタ版 → PNG」「アイコン → SVG」。この3行を決めておくだけで、事故の大半は防げます。逆にやりがちな失敗が、ドキュメントのスクリーンショットをJPEGで出すこと。クロマサブサンプリングとリンギングで文字が二重に汚れます。

「PNG最適化」が2種類あることを知っておく。 oxipngzopflipng は、フィルタの選び方とDeflateの探索を変えるだけの完全可逆な再圧縮で、見た目は1ビットも変わりません。一方 pngquant はパレット(256色以下)へ減色する非可逆な処理です。名前が似ていて混ざりやすいので、CIに組み込むときは必ずどちらか確認してください。減色は写真では階調が破綻しますが、UI素材では見た目をほぼ保ったまま数分の1になることもあります。

インターレース(Adam7)は既定でオフにする。 7回に分けて粗い画像から届ける仕組みですが、データの並びが分断されて予測が当たりにくくなるため、ファイルサイズはむしろ増えます。「早く見え始める」効果が本当に必要な場面以外では使いません。

メタデータを落とす。 PNGはテキストチャンクやカラープロファイルを保持できます。編集ソフトが埋めたコメントやサムネイルが数十KB残っていることがあり、小さなアイコンでは本体より大きいことすらあります。配信前の最適化ステップで剥がします。

ビット深度と色型を確認する。 16ビット/チャンネルで書き出したPNGは、8ビットの2倍の容量になります。UI素材でこれが必要になることはまずありません。同様に、256色以内で足りる画像がトゥルーカラーで保存されていないかも見る価値があります。

設計レビューで問われるのは「なぜPNGは写真に向かないのか」です。可逆である以上、センサノイズという予測不能な成分を捨てられないから——この一文で説明が付けば十分です。

まとめ

次は、ここで出てきた「よく出る記号を短く」の原理そのもの——エントロピー符号化を、ハフマンから算術符号まで見ていきます。

コメント

コメントにはログインが必要です