PNGのデコードプロセスをステップごとに解説 (2022年)
Each step of decoding a PNG (2022) (iter.ca)
要約
この記事は、PNG(Portable Network Graphics)ファイルのデコードプロセスを詳細に解説しています。PNGファイルはチャンクと呼ばれる単位で構成されており、各チャンクは長さ、タイプ、データ、CRC(誤り検出符号)を持ちます。特にIDATチャンクには圧縮された画像データが含まれており、zlibライブラリを使用して解凍されます。さらに、画像表示を最適化するためのフィルタリング、ガンマ補正、色空間情報、インターレースなどの機能についても説明されています。
全文翻訳
PNGのデコードの各ステップ
PNGファイルをデコードする完全なプロセスを見ていきましょう。デモンストレーションに使用する画像はこちらです。
[画像: 互いに重なり合った複数の「S」の文字。それぞれ色が異なる。]
チャンク化
PNGファイルは、一連のチャンクとして保存されます。各チャンクは、4バイトの長さ、4バイトの名前、実際のデータ、およびCRC(巡回冗長検査)で構成されます。ほとんどのチャンクは画像のメタデータを含みますが、IDATチャンクには実際の圧縮画像データが含まれています。チャンク名の各文字の大文字・小文字は、予期しないチャンクをどのように処理すべきかを決定します。
| UPPERCASE | lowercase |
|---|---|
| Critical | Ancillary |
| Public | Private |
| Reserved | Reserved |
| Unsafe to copy | Safe to copy |
[画像: c: Ancillary, H: Public, R: Reserved, M: Unsafe to copy]
この画像に含まれるチャンクは以下の通りです。
メタデータチャンクを表示
これは画像をPNGとして識別します。改行文字やその他の文字が含まれており、バイナリ画像データが破損していないことを保証するためにしばしば変更されます。
89 50 4E 47 0D 0A 1A 0A
これは画像に関するメタデータを含みます。
長さ 00 00 00 0D = 13バイト
タイプ 49 48 44 52 = IHDR
データ 00 00 01 00 00 00 01 00 08 02 00 00 00
| 幅 | 256 |
| 高さ | 256 |
| ビット深度 | 8 |
| カラースペース | Truecolor RGB |
| 圧縮方法 | 0 |
| フィルタ方法 | 0 |
| インターレース | 無効 |
CRC D3 10 3F 31
これには圧縮された画像データが含まれます。
長さ 00 00 F5 51 = 62801バイト
タイプ 49 44 41 54 = IDAT
データ ... (画像の圧縮およびフィルタリングされたバイト)
CRC BF EB 1B 15
この空のチャンクはPNGファイルの終端を示します。
長さ00 00 00 00 = 0バイト
タイプ49 45 4E 44 = IEND
CRC AE 42 60 82
画像データの抽出
すべての IDAT チャンクを取得し、それらを連結します。この画像では、IDAT チャンクは1つだけです。一部の画像には複数の IDAT チャンクがあり、その内容はPNGデコーダーによって連結されます。これは、ストリーミングエンコーダーが事前に総データ長を知る必要がないためです。各チャンクの先頭にデータ長があるからです。また、画像データ長が最大のチャンクサイズ(2554)を超える場合に複数の IDAT チャンクが必要になります。圧縮データを生の画像データとして扱った場合の結果は以下のようになります。
[画像: 上部3分の1はランダムなノイズ、残りは黒。]
ランダムなノイズのように見えますが、これは圧縮アルゴリズムがうまく機能したことを意味します。圧縮データは、エンコードされる低エントロピーデータよりも高エントロピーに見えるはずです。また、実際の画像の高さの3分の1未満です。これはかなりの圧縮率です!
解凍
最も重要なチャンクは IDAT チャンクで、実際の画像データが含まれています。フィルタリングされた画像データを得るために、IDAT チャンクのデータを連結し、zlibを使用して解凍します。ここでは画像固有の圧縮メカニズムは使用されていません。通常のzlib圧縮のみです。
[画像: 例の画像。上から下にかけて、ますます右に偏っている。ほとんど白黒で、強烈な色のピクセルが散在している。]
色の見た目がすべて間違っていることに加えて、画像は水平方向に歪んでいるようにも見えます。これは、画像の各行の最初のバイトがフィルタであるためです。フィルタは直接画像サイズを削減するわけではありませんが、zlibで圧縮しやすい形式にします。以前にPNGフィルタについて書いたことがあります。
フィルタ解除
解凍されたデータを取り、各行のフィルタを元に戻します。これにより、元の画像と同じデコードされた画像が得られます。各フィルタタイプの人気度は以下の通りです。
None 0
Subtract 23
Up 77
Average 134
Paeth 22
[画像: 例の画像]
インターレース
PNGはオプションでインターレースできます。これは、画像を7つの異なる画像に分割し、それぞれが画像の重複しない領域をカバーします。
[画像: 8x8領域における7つのパスの図。最初は左上の1ピクセル、7番目は2行おき。]
7つの画像は順番に読み込まれ、読み込みが進むにつれて画像の詳細が増していきます。この画像のシーケンスはAdam7と呼ばれます。
7つの画像それぞれのデータは、画像ファイル内で互いの後に格納されます。例の画像を取得し、インターレースを有効にした場合の生の非圧縮画像データは以下のようになります。
[画像: 前の歪んだ「S」の文字と同じだが、複数積み重なっている。下半分には2つ、その上の3分の1にはさらに2つあり、それより上はますますランダムなノイズのように見える。]
この画像データから抽出できる7つのパスは、画像の縮小版のように見えます。
[画像] [画像] [画像] [画像] [画像] [画像] [画像]
これらのサブ画像の一部は長方形ですが、実際の画像は正方形であるため、画像を読み込む際には水平方向の詳細が垂直方向の詳細よりも多くなります。これは、人間の視覚的知覚にとって水平方向の詳細が垂直方向の詳細よりも重要であるためです。
ボーナス: バグ
Averageフィルタで、整数オーバーフローの計算方法が(他のものとは少し異なるように指定されている)オーバーフローを誤って処理した場合にどうなるかを示します。
[画像: 例の画像だが、4分の1ほど下からグリッチが発生しているように見える。]