PNM(PPM/PGM/PBM)ファイルフォーマット

作成:

最初に取り上げるのが、PNM (PPM / PGM / PBM) 画像形式である。 以前説明したことがある画像形式だが、改めて説明しようと思う。

この形式は名前がちょっと複雑なことになっている。 全体を総称して、PNM (Portable aNyMap)形式と呼ばれるものなのだが、 格納する形式によって、呼び名が異なっている。 モノクロ(白黒2色)の画像を格納する PBM (Portable BitMap)形式、 グレースケールの画像を格納する PGM (Portable GrayMap)形式、 フルカラーの画像を格納する PPM (Portable PixMap)形式、 という区別になっている。 アルファチャンネルには対応していない。

それぞれは別の画像形式というよりは、同一の画像形式のモードが違うもの、といった方がしっくり来るのだが、 拡張子もそれぞれ.ppm/.pgm/.pbmと分かれているのが通常だ。 全部を合わせた、pnmという拡張子もあるにはあるが、私はあまり見かけたことはない。

また、この形式の画像を扱うためのライブラリ・ツール群として Netpbmというものがあり、 画像形式自体がこのツールとともに作られていった経緯から、Netpbm形式などと呼ばれることもある。

今回説明するPNMの範囲外になるが、PNMを拡張した形式で、 PAM (Portable Arbitrary Map)というフォーマットがある。 こちらはアルファチャンネルにも対応している。

画像形式の一例

もともとこの形式は、電子メールなどでやりとりでき、特別なツールがなくても読み書き編集ができる、 「プレーンテキスト」の画像形式として提唱されたところから始まっている。 つまり、テキストエディタさえあれば読み書き編集が可能な形式である。

テキストであるということは画像のようなデータを格納する上で非常に効率が悪いのだが、 初級者にも非常にわかりやすく、学習にはぴったりである。

どのぐらい分かりやすいか以下のデータを見て欲しい。PBM形式の一例である。

P1
15 15
1 1 1 1 1 0 1 1 1 1 1 1 1 1 1
1 1 1 1 1 0 1 1 1 0 0 0 1 1 1
1 1 0 0 0 0 0 0 0 1 1 1 1 1 1
1 1 1 1 1 0 1 1 1 1 1 1 1 1 1
1 1 1 1 1 0 1 1 0 1 1 1 1 1 1
1 1 1 1 1 0 0 0 0 0 0 1 1 1 1
1 1 1 1 0 0 1 1 0 1 1 0 0 1 1
1 1 0 0 1 0 1 1 0 1 1 1 0 1 1
1 1 0 1 1 0 1 0 1 1 1 1 1 0 1
1 0 1 1 1 0 1 0 1 1 1 1 1 0 1
1 0 1 1 1 0 0 1 1 1 1 1 1 0 1
1 0 1 1 1 0 0 1 1 1 1 1 0 1 1
1 1 0 0 0 0 1 1 1 1 1 0 0 1 1
1 1 1 1 1 0 1 1 0 0 0 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1

どのような画像かわざわざ説明しなくても予想ができると思うが、以下にこれを拡大してpngに変換した画像を示す。

PBM形式の例

実際、このPBMファイルは画像編集ソフトで作ったわけではなく、私がテキストエディタで入力して作ったものだ。 コピーアンドペーストでテキストエディタに入力し拡張子を.pbmとして保存すれば画像ファイルとして通用する。

どうだろうか、おそらく入門書を読んでいる途中の初級者でも、 ファイル入出力あたりまで進んでいれば、なんとなくどうすればこのデータの読み書きができるか想像できるだろう。

現在インターネット上などで通常流通することは少なく、 一般的なユーザで見たことがあるという人は少ないのではないかという形式を 一番最初に取り上げた理由が分かっていただけたと思う。

しかし、逆に少なくともWindows環境では対応しているソフトウェアが少ないので、 扱おうと思うと別途ソフトをインストールする必要がある。 確実なのはGIMPだろう。 オープンソースで無料で使えるし、最近はWindows用のバイナリも公式で配布してくれているので特に苦労せずに使えるはずだ。 私が使っているツールだと、Paint Shop Proも対応していたので紹介しておく。 一方、UNIX系のOSでは普通に使われる画像形式なので、画像を扱うためのツール群の多くは標準でサポートしているだろう。 必要ならGIMPを使えばよい。

画像形式 -ヘッダ-

具体的な画像形式について説明していく。 公式のファイルフォーマットドキュメントは以下を参照

英語ではあるが、わかりやすく明確に仕様が記述されているので、 最終的にはこちらのドキュメントを参照してもらった方が良いだろう。

前項で、この画像形式はプレーンテキストという説明から入ったが、 さすがに全てテキストでは効率が悪すぎるので、バイナリで格納するモードも持っている。 このバイナリーで格納するモードでも、バイナリなのは画像そのものの領域で、ヘッダ領域はプレーンテキストになっている。

まずは、このヘッダ領域のフォーマットについて説明する。

ヘッダは以下の様な構造になっている。

P<x>
# comment
<width> <height>
<max>

はじめのPxと書いているところは、ファイル形式の種別を示すマジックナンバーとなっている。

マジックナンバー種別拡張子画像種別データ形式
P1PBM.pbmモノクロ(白黒2色)テキスト
P2PGM.pgmグレースケールテキスト
P3PPM.ppmフルカラーテキスト
P4PBM.pbmモノクロ(白黒2色)バイナリ
P5PGM.pgmグレースケールバイナリ
P6PPM.ppmフルカラーバイナリ

テキスト形式は、ASCII形式、バイナリ形式は、RAW形式と呼ばれることもある。 テキストで記述された、ある意味でゆるいフォーマットではあるが、 このマジックナンバーだけは必ずファイルの先頭にある必要がある。

さて、マジックナンバーの次からが、 厳密にプログラムで表現するのが面倒なところになる。

人間にも読み書きできるような、ある意味「ゆるい」フォーマットというのは、 パッと見た感じでは簡単だが、プログラム上で完全に対応するのは逆に難易度が高い。 例えば、AとBの間に空白を挟むというと、普通はスペースを開けるだろうが、 スペースを2個以上入れる人や、改行、タブを交える人もいるだろう。 そのように一定のルールのもと、あらゆるパターンに対応させる必要がある。

マジックナンバー2文字の次だが、例では改行を入れている。 通常は改行が入っている場合が多いが、仕様上は改行とは限らない。 PNM形式ではテキスト領域のトークン(要素)のデリミタ(分離記号)は1つ以上の空白文字になっている。 空白文字とは、C言語で標準空白類文字とされる文字だ。 列挙すると以下が該当する文字だ。

  • ' ' 空白(space)
  • '\r' 復帰(CR)
  • '\n' 改行(LF)
  • '\t' 水平タブ(TAB)
  • '\v' 垂直タブ(VT)
  • '\f' 書式送り(FF)

これはC言語の標準関数である、isspace()が真を返す文字だ。

#include <ctype.h>
int isspace(int c);

多くの場合、改行一つだが、スペースでもタブでも、これらの複数の組み合わせであっても違反ではないので、 どの場合でも読み込める必要がある。

続いて、マジックナンバーの次の行に書いている#から始まる行はコメントだ。 コメントは何もここにだけ現れるとは限らず、テキストの領域であればどこに現れてもよい。 ヘッダだけでなく、テキスト形式の場合は画像データ中に現れても仕様違反ではない。 コメントの範囲を厳密に言うと、#で始まり、次の最初の改行、 つまり\nか、\rが現れるまで、がコメントだ。 コメントには画像を出力したツールの名前などが書かれていたりする。

コメントの次の行には画像の幅と高さを10進数で記述されている。 多くの場合、幅と高さが1行の中に書かれ、間にスペースが挟まれているが、 前述のとおり、幅と高さの間に改行があったり、マジックナンバーと同じ行にあっても間違いではない。

そして最後に、輝度の最大値を10進数で記述する。 この行は2値しか取らないPBM形式には不要なため存在しない、PGM形式とPPM形式は指定する必要がある。 一般に利用されるRGB値などの最大値は、8bitの最大値である255だが、 この画像形式では任意の数字を指定できる。例えば9と書けば0~9の10階調で表現することができる。 もともとは上限255、つまり8bitまでの対応となる仕様だったが、 現在、仕様が拡張され上限65535で、いわゆる16bit深度までの階調を扱える仕様になっている。 ただ、他の多くのフォーマットもビット深度は8bitまでであることも考えれば、 255より大きな値になっているファイルを読み込まなければならないシーンは稀だろう。

まとめると以下の様な構造になる。

  • マジックナンバー
  • 空白
  • 空白
  • 高さ
  • 空白
  • (PPM/PGM)最大輝度
  • (PPM/PGM)空白

最後の空白については、テキスト形式の場合特に気にする必要はないが、 バイナリ形式の場合は、ここがテキスト形式とバイナリ形式の境目になるため注意が必要だ。 バイナリ形式では最後の空白は一つと厳密に決まっている。 多くの場合改行で表現されるのだが、Windowsの場合、テキストモードで改行を出力するとCRLFの2byteに変換されてしまう。 そのため、プレーンテキストの領域とはいえ、バイナリモードで扱うようにする必要がある。

画像形式 -画像データ-

ヘッダ情報に引き続き配置されるのが画像データである。 詳細は各画像形式ごとに説明していくが、共通しているのが、 データの格納順は横書き文章と同一で、左上から右側へ走査しながら下に向かう形で格納されている。 また、テキスト形式は1行の長さは70文字以下であることが推奨されている。 実際には70文字以上あっても読み込んでくれるツールがほとんどだ。

P1(PBM形式)

白黒2色のテキスト形式。0が白、1が黒として、順にテキストで格納される。 この形式でややこしいのが、ピクセルの表現に使用される文字は01の2種類しかないため、 デリミタとしての空白は必ずしも必要ない。 隙間なく01が連なっていてもよいし、間に空白があってもよい。 読み込む側を作ることを考えると、どちらでも良いという仕様は面倒である。

P2(PGM形式)

グレースケールのテキスト形式。指定した最大値までの数値を10進数のテキストで順に格納する。 数値の桁数は決まっていないため、数値と数値の間にはデリミタが必要である。

P3(PPM形式)

フルカラーのテキスト形式。指定した最大値までの数値で、RGBの順に10進数のテキストで順に格納する。 数値の桁数は決まっていないため、数値と数値の間にはデリミタが必要である。

P4(PBM形式)

白黒2値のバイナリ形式。0が白、1が黒とするのはテキスト形式と同じであるが、1bitで表現可能なので、 上位ビットから順に、1byteに8ピクセル分格納する。 つまり、左から「白白白白黒黒黒黒」であれば、0x0Fと格納される。 また、画像の幅が8の倍数でない場合は、最後の1byteに余地が残るが、次の行のデータはつめ込まない。 つまり、幅4の画像で「白白白白」、次の行が「黒黒黒黒」であれば0x0fと1byteにまとめてしまうのではなく、 0x00,0xf0と行ごとに分離する。

P5(PGM形式)

グレースケールのバイナリ形式、指定した最大値までの数値を1ピクセル1byteで順に格納する。 例えば最大値が15だとすると、1ピクセルを表現するのに4bitあればよく、 1byteに2ピクセル分格納できるが、そのようなことは行われず、必ず1byte使用する。 また、最大値が256以上の場合2byte必要になるが、その場合はビッグエンディアンで格納される。

P6(PPM形式)

フルカラーのバイナリ形式、指定した最大値までの数値で、RGBの順に1byteずつ、1ピクセル3byteで順に格納する。 P5と同じで3byte以下で表現できる場合もまとめるようなことは行われない。 また、最大値が256以上の場合も同様に、ビッグエンディアンの2byteで格納される。