PNGの規格を勉強する機会があったので、その内容を簡単に説明します*1。
PNGはいくつかの"チャンク"が集まって構成されています。例えば、IHDR
チャンクやIDAT
チャンク、PLTE
チャンクなどがあります。では、PNGファイルという単なるバイト列から、どのようにチャンクを抽出すれば良いのでしょうか?
これは、PNGファイルの構造を知ることでわかります。
PNGファイルの構造
PNGファイルの構造は、以下のようになっています:
- ファイル先頭の8byteは 必ず
137, 80, 78, 71, 13, 10, 26, 10
である - それ以降のバイト列は、次の構造の繰り返しである
この構造に従ってPNGファイルをパースすると、複数のチャンクを取りだすことができます。
あとはこれらのチャンクを画像として解釈すればいっちょう上がりというわけです。
重要なチャンク
画像データを取得するために必須のチャンクについて解説します。
IHDRチャンク
まず、チャンクの中から、IHDR
チャンクを探してきましょう。こいつはいわゆるヘッダーです。
Width
, Height
, Color Type
の3つのデータが含まれています*2。
このColor Type
というのが何を表しているのかというと、
- "パレット"を使っているか
- 白黒画像なのか、カラー画像なのか
- アルファチャンネルが使用されているか(透過画像かどうか)
という3つの情報を表しています。
PLTEチャンク
Color Type
を見て、"パレット"が使われているというのであれば、必ずPLTE
チャンクが存在します。
このPLTE
チャンクというのは、「画像内で使われ得るすべての色」の情報を持っています。
例えば、赤一色の画像であればrgb = [255,0,0]
という値だけを持ちますし、赤と白の二色だけの画像であればrgb = [255, 0, 0]
に加えてrgb = [255,255,255]
という値も持ちます。また、アルファチャンネルが使用されている場合は、RGBの三色に加えてもう一つ「透過度」を表すbyteが存在します。例えば、「赤色で全く透明ではない色」は[255, 0, 0, 255]
になるわけですね。
PLTEチャンクのバイト列から色情報を取得するのは簡単で、RGBなら3つづつ(RGB+αなら4つづつ)先頭のバイトからグループ化していけば良いです。
なんでこんなものが存在するのかというと、その方が画像のサイズを落とせるからです。例えば、3色しか使われない画像を圧縮するとき、すべてのピクセルごとに色情報を持つのは容量の無駄です。
それよりも、その3色にそれぞれ番号を振っておいて、「このピクセルはn番の色、このピクセルはm番の色、このピクセルは...」というようにデータを持っておくほうが、使う色が少ない場合は容量が少なくて済みます。
IDATチャンク
ここが画像データの本体です。IDAT
チャンクには、実際の画像データが格納されています*3。
IDAT
チャンクに格納されている画像データはDeflate圧縮されています。そのため、IDAT
チャンクから画像データを取得するためには展開する必要があります*4。展開されたデータは、👇のように、左上から右下に向かって1ピクセルづつ色の情報が並んでいます*5。
画像データを取得する
ここまでのチャンクを解釈して画像データを取得するわけですが、結構複雑です。
"パレット"を使用する場合
この場合は簡単で、画像データを1byteづつ読んで、その値を「パレットのインデックス」として解釈します。
例えば、PLTEのデータがRGBs = [[255, 0, 0], [0, 255, 0], [0, 0, 255]]
であり、IDATのデータがpixels = [0, 1, 2, 0, 1, 2]
であれば、画像の色データはcolors = [[255, 0, 0], [0, 255, 0], [0, 0, 255], [255, 0, 0], [0, 255, 0], [0, 0, 255]]
になるということです*6。
"パレット"を使用しない場合
この場合は、IDAT
のデータを直接解釈します。ここが結構複雑です。
まず、IDAT
のバイト列をHeight
の数に分割します。例えば、Height
が100pixelの画像であれば、100個の等しい長さのバイト列に分割するわけです。
そして、ここがややこしいのですが、各バイト列の先頭1byteはfilter method
を表しています。従って、色情報そのものは2byte目から始まるわけです。filter method
は、None
, Sub
, Up
, Average
, Paeth
の5種類があり、それぞれ違う方法で解釈しないと正しい色情報が得られません。
ところで、filter method
は各行ごとに設定されています。従って、各行ごとに違うfilter method
を使うということができます。
実際に、Portable Network Graphics - Wikipediaにデモ画像として掲載されているサイコロの画像(👇)は、行ごとに異なるfilter methodが設定されているようです。
まとめ
PNGファイルの規格の説明は以上です。
あとはプログラムを書けば、実際のPNGファイルを解釈することができます。
詳解 OpenCV 3 ―コンピュータビジョンライブラリを使った画像処理・認識
- 作者: Gary Bradski,Adrian Kaehler,松田晃一,小沼千絵,永田雅人,花形理
- 出版社/メーカー: オライリージャパン
- 発売日: 2018/05/26
- メディア: 単行本(ソフトカバー)
- この商品を含むブログを見る