C言語プロジェクト演習課題 PNM画像ファイル

windowsOS coskx 20180311

1 PNMファイル概要とCプログラムでの表現
1.1 PNMファイル

PNMファイルは1980年台のInternetが発達していく過程でメールに画像を添付する目的で作成された画像保存形式である。 白黒画像やグレイスケール画像,RGBカラー画像を簡単な形式で表している。 元祖PNMファイル形式はその後使いやすく拡張され,NetPNM形式として知られている。 画像圧縮でファイルサイズを小さくすると言った考えはあまりなかった時代のものなので,最近では使われていない。
しかし,形式が単純なのでC言語プログラミングの課題などに時々使われている。

PNMファイルでは,白黒二値画像形式,グレイスケール画像形式,RGBカラー画像はそれぞれPBM, PGM, PPM形式と呼ばれる。 3つの形式それぞれにASCII形式(テキスト形式)とraw形式(バイナリ形式)での保存があるため,全部で6形式の画像ファイル形式がある。
PNMの各ファイルは,形式や画像サイズなどを表すヘッダとそれに続く画像データでできている。
本演習では,ASCII形式(テキスト形式)のPNM形式を扱う。

形式名称の補足説明
PNMは「Portable aNyMap」 という画像形式を表しており,次の3つのフォーマットの総称である。
(1)PBM Portable Bitmap forMat 2値白黒画像フォーマット
(2)PGM Portable Graymap forMat グレイスケール白黒画像フォーマット
(3)PPM Portable Pixmap forMat カラー画像フォーマット

ダウンロード
この後で使うファイルをダウンロードしておこう(PNMビューアを含みます)    ダウンロード

1.2 PBM形式ファイル

PBM形式ファイルは,一番単純な2値白黒画像を表現するファイルである。テキストファイルなので,テキストエディタで開いて内部を見ることができる。
File 1.2.1はテキストエディタで表示しているファイルturtle.pbmを示している。
PNMビューア(ここではダウンロードしたzipファイル中のPNMViewer.exeを使う。)を用いてこのファイルを表示すると, 図1.2.1のように白黒2値画像(中間的な灰色はない)の亀になる。
ファイルturtle.pbmはテキストファイルであるが,よく見ると亀が見える。図1.2.1の黒い部分が1,白い部分が0になっているのがわかるであろう。

ファイルturtle.pbmの最初の2行には「P1」「64」「39」が書いてあるのが見える。
この部分はPNMヘッダと呼ばれてる。
「P1」はこのファイルが2値白黒画像ASCII形式(テキスト形式)であることを表し,「マジックナンバー」と呼ばれている。 続く「64」「39」は横画素数(ピクセル数)が64で縦画素数が39であること,すなわち画像の大きさが64×39であることを示している。
この2行の後に続くのが,0または1の,64×39個の画素値である。

本来ならば,PNM形式のテキストファイルは1行が70文字以下になるように適当に改行されていなければならないことになっている。 しかし,画像イメージをテキストエディタで見えるようにするため,File 1.2.1 のPBMファイルはこの規格に反したファイルになっている。 ビューワが対応してくれるので,細かなことは気にしないでおこう。
規格通りのPBMファイルでは「画像の横幅」と「ファイル中の1行の長さ」が異なってしまうが, 画像の横幅はヘッダに書いてあるので,そのことでこまることはない。

File 1.2.1 PBMファイル turtle.pbm

P1
64 39
 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
 0 0 0 0 0 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
 0 0 0 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
 0 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
 0 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
 0 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0
 0 0 0 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0
 0 0 0 0 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0
 0 0 0 0 1 1 1 1 1 1 1 1 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0
 0 0 0 0 1 1 1 1 1 1 1 1 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0
 0 0 0 0 1 1 1 1 1 1 1 1 1 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0
 0 0 0 0 1 1 1 1 1 1 1 1 1 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0
 0 0 0 0 0 1 1 1 1 1 1 1 1 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0
 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0
 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0
 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0
 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0
 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 0 0
 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0
 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0
 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0
 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 1 0 1 0 1 0 1 0 0 0 0 0 0 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0


図1.2.1 PNMビューア(PNMViewer.exe)で turtle.pbm を見る
 (見たいファイルのアイコンをこのアプリにドロップする)

1.3 PGM形式ファイル

次にグレースケール画像を表現するPGMファイルを見てみよう。これもテキストファイルなので,テキストエディタで開いて内部を見ることができる。
File 1.3.1はテキストエディタで表示しているファイルturtle.pgmを示している。ここに出てくる値は0から255までの値である。
PNMビューアを用いてこのファイルを表示すると,図1.3.1のようにグレイスケール画像となる。
ファイルturtle.pgmはテキストファイルであるが,よく見ると亀が見える。(左上の亀の頭は良く見える。)
図1.3.1の暗い部分が小さな値,明るい部分が大きな値になっているのがわかるであろう。

ファイルturtle.pgmの最初の3行には「P2」「64」「40」「255」が書いてあるのが見える。
この部分もPNMヘッダである。
「P2」はこのファイルがグレイスケール画像ASCII形式(テキスト形式)であることを表し,これも「マジックナンバー」である。 続く「64」「40」は横画素数(ピクセル数)が64で縦画素数が40であることを示している。
最後の「255」は各点の画素値(画素の明るさ値)のとりうる値の最大値が255であることを示している。(通常最大値はは255が使われる。画素値は0から255までの範囲にある)
この3行の後に続くのが,0から255までの値を持つ,64×40個の画素値である。

(本来ならば,PNM形式のテキストファイルは1行が70文字以下になるように,改行されていなければならない,細かなことは気にしないでおこう。)

File 1.3.1 PGMファイル turtle.pgm (横方向に長いので注意)

P2
64 40
255
 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224  64  68  72  76 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224
 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224  63  64  64  63  68  71  76  79  84  88  92  96 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224
 224 224 224 224  63  63  64  63  63  63  63 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224  63  63  64  64  63  66  71  74  78  82  87  91  95  98 103 107 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224
 224 224 224  64  63  63  64  63  64  63  64  63  64 224 224 224 224 224 224 224 224 224 224 224 224 224 224  64  64  63  63  64  66  69  74  78  82  86  90  94  98 101 107 110 114 118 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224
 224 224  64  63  64  63  63  63  64  63  63  64  64 224 224 224 224 224 224 224 224 224 224 224 224  63  63  64  64  64  63  64  68  72  76  81  85  89  93  97 101 105 109 114 118 122 126 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224
 224  63  64  64  64  64  63  63  63  63  63  63  63  64 224 224 224 224 224 224 224 224 224 224  63  63  63  63  64  64  63  68  71  76  79  84  88  91  96 100 104 109 113 117 120 124 129 133 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224
 224  63  63  63  63  63  64  63  63  63  64  63  63  63 224 224 224 224 224 224 224 224 224  64  64  64  63  63  63  63  66  71  75  79  83  87  91  95  99 104 107 111 116 120 123 128 132 136 140 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224
 224  64  63  64  63  63  63  64  63  64  64  64  63  64 224 224 224 224 224 224 224 224  63  63  63  64  63  63  64  66  69  74  77  82  86  90  94  99 102 107 110 115 118 123 126 132 135 139 144 147 224 224 224 224 224 224 224 224 224 224 224 224 224 224
 224  63  63  63  64  63  64  64  64  64  63  63  64  63 224 224 224 224 224 224 224  63  63  64  63  64  64  63  64  68  73  77  81  86  89  94  97 102 105 110 113 118 122 127 130 134 138 143 146 151 154 224 224 224 224 224 224 224 224 224 224 224 224 224
 224  64  64  63  64  64  63  63  64  63  64  63  63  64 224 224 224 224 224 224  63  63  64  64  64  63  63  64  67  72  76  80  83  89  92  96 100 105 108 113 116 121 125 129 133 138 141 145 150 154 158 162 224 224 224 224 224 224 224 224 224 224 224 224
 224 224 224  63  63  63  63  63  63  64  63  64  64  64 224 224 224 224 224 224  63  63  64  64  64  64  63  66  71  75  79  83  87  91  96  99 103 108 112 116 120 124 128 132 136 141 144 149 153 157 161 166 169 224 224 224 224 224 224 224 224 224 224 224
 224 224 224 224  64  64  63  63  64  64  63  63  63  64 224 224 224 224 224  63  64  64  64  64  63  64  66  69  74  77  82  86  91  94  99 103 107 111 115 119 124 127 131 135 139 144 148 152 156 160 164 168 172 224 224 224 224 224 224 224 224 224 224 224
 224 224 224 224  64  64  63  63  64  63  63  64  63  64 224 224 224 224  63  63  64  63  63  63  63  65  69  72  77  80  85  89  94  98 102 105 110 113 118 122 127 130 134 138 143 147 151 155 159 163 167 171 175 179 224 224 224 224 224 224 224 224 224 224
 224 224 224 224 224  63  63  63  63  64  64  64  63  63 224 224 224  64  64  63  63  64  63  64  64  68  72  76  80  84  89  92  96 100 105 108 113 116 122 125 129 133 138 142 146 150 154 158 162 167 171 174 179 183 187 224 224 224 224 224 224 224 224 224
 224 224 224 224 224  64  64  63  64  63  64  63  63  63 224 224 224  64  63  64  63  63  63  63  67  71  75  79  83  87  92  95 100 104 108 111 116 120 125 128 132 137 141 144 149 153 157 161 165 170 174 177 182 186 190 194 224 224 224 224 224 224 224 224
 224 224 224 224 224  64  64  64  63  63  63  64  63  63 224 224  64  63  63  63  63  64  64  66  70  74  78  82  86  91  94  99 103 107 111 115 119 123 127 131 136 140 144 147 153 156 160 164 169 173 177 180 185 189 193 197 224 224 224 224 224 224 224 224
 224 224 224 224 224  64  64  64  64  64  63  63  63  63  64  64  63  64  64  64  64  64  64  69  73  78  81  85  89  94  98 101 106 110 114 118 122 126 131 135 138 143 147 151 156 159 164 167 172 175 180 184 188 192 197 200 200 224 224 224 224 224 224 224
 224 224 224 224 224  63  64  63  64  64  64  63  64  64  64  64  63  64  64  64  63  64  68  72  76  80  84  89  92  97 101 105 109 113 117 121 125 129 133 138 142 146 150 154 158 162 166 171 175 179 183 187 191 195 199 200 200 200 224 224 224 224 224 224
 224 224 224 224 224 224  63  64  64  63  64  63  64  64  63  63  64  63  64  64  63  67  71  76  79  83  87  92  96 100 104 108 112 117 120 125 128 132 137 141 145 149 153 157 161 166 169 174 177 182 186 190 194 199 200 200 200 200 200 224 224 224 224 224
 224 224 224 224 224 224  63  64  63  63  64  63  64  64  63  63  63  64  64  64  66  70  74  78  82  87  91  95  98 103 107 112 115 120 124 127 131 136 140 145 148 153 156 161 164 169 173 177 181 185 189 193 198 200 200 200 200 200 200 224 224 224 224 224
 224 224 224 224 224 224  64  63  63  63  63  63  64  63  64  63  64  64  64  65  69  73  77  81  86  89  94  97 101 106 110 114 118 123 126 130 135 139 143 147 151 155 159 164 168 172 176 180 184 189 192 197 200 200 200 200 200 200 200 224 224 224 224 224
 224 224 224 224 224 224 224  63  63  64  63  64  64  64  63  63  63  63  64  68  72  77  81  85  88  92  97 101 106 109 113 118 122 126 129 134 138 143 146 150 154 159 162 167 170 175 179 183 187 192 195 200 200 200 200 200 200 200 200 200 200 200 200 200
 200 224 224 224 224 224 224  63  63  63  63  63  64  63  63  64  64  63  67  71  76  80  84  88  92  96 100 104 108 112 116 120 125 128 133 137 142 145 149 154 157 162 166 169 174 178 182 186 190 194 198 200 200 200 200 200 199 200 200 200 200 200 200 200
 200 224 224 224 224 224 224 224  63  63  64  64  63  64  63  63  64  66  71  74  79  82  87  91  95  99 104 107 111 115 120 123 128 132 136 140 144 149 152 156 161 165 169 173 177 181 185 190 194 197 200 200 200 200 200 200 200 200 200 199 200 200 200 200
 200 224 224 224 224 224 224 224 224  63  64  63  63  63  64  64  65  69  74  78  82  86  90  94  98 102 107 110 115 118 123 127 131 135 140 143 147 151 155 160 164 168 172 176 180 185 189 192 197 200 200 200 199 200 200 200 200 200 200 200 200 200 200 200
 199 224 224 224 224 224 224 224 224 224  63  64  64  64  64  64  68  72  76  80  84  88  93  97 102 106 109 113 118 121 126 130 134 138 142 146 150 155 159 162 167 171 175 179 183 187 192 196 200 200 200 200 200 200 200 200 200 200 200 200 200 200 200 200
 224 224 224 224 224 224 224 224 224 224 224  63  64  64  64  67  72  76  79  84  88  92  96 100 104 108 112 116 121 125 129 133 137 141 145 149 154 158 162 166 170 174 179 182 187 190 195 198 200 200 200 200 200 200 200 200 200 200 200 200 200 200 224 224
 224 224 224 224 224 224 224 224 224 224 224 224 224  64  66  71  75  79  83  87  91  95  99 104 107 111 116 120 124 128 132 137 141 144 148 152 156 161 165 169 173 176 182 185 190 193 198 200 199 200 200 200 199 200 200 200 200 200 200 200 224 224 224 224
 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224  78  82  86  91  94  99 102 106 110 114 118 123 126 131 135 139 143 147 152 156 160 164 168 172 176 181 184 189 192 197 200 200 200 200 200 200 199 200 200 200 224 224 224 224 224 224 224 224
 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224  89  93  97 101 105 110 114 118 122 126 130 134 138 142 147 151 154 159 163 167 171 175 179 184 187 191 196 200 200 200 200 200 200 200 200 200 224 224 224 224 224 224 224 224 224 224
 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224  92  97 100 105 109 113 116 121 125 129 133 138 141 146 149 154 158 162 166 170 175 178 182 187 190 195 199 200 200 200 200 200 200 200 224 224 224 224 224 224 224 224 224 224 224 224
 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224  91  95  99 103 107 112 116 120 124 128 132 136 141 144 149 153 157 161 165 169 174 177 182 186 190 194 198 200 200 200 200 200 200 200 199 200 224 224 224 224 224 224 224 224 224 224 224
 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224  90  94  99 102 107 111 115 119 123 127 224 224 224 143 147 152 156 160 164 168 172 176 181 185 188 193 197 224 200 200 200 200 200 200 200 200 200 200 224 224 224 224 224 224 224 224 224 224
 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224  89  93  98 102 106 110 114 118 122 126 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 200 200 200 200 200 200 200 200 199 200 224 224 224 224 224 224 224 224 224
 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224  92  97 101 105 108 113 117 121 125 129 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 200 200 200 200 200 200 200 200 200 200 200 224 224 224 224 224 224 224 224
 224 224 224 224 224 224 224 224 224 224 224 224 224 224  92  96 100 104 108 112 116 120 124 129 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 200 199 200 200 200 200 199 200 200 200 224 224 224 224 224 224 224 224
 224 224 224 224 224 224 224 224 224 224 224 224 224 224  94  98 103 107 111 115 119 123 127 131 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 200 200 200 200 199 199 200 200 200 200 224 224 224 224 224 224 224 224
 224 224 224 224 224 224 224 224 224 224 224 224 224 224  97 102 106 110 114 118 123 126 131 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 200 200 200 200 200 200 200 200 200 224 224 224 224 224 224 224 224
 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 105 108 113 117 121 126 130 133 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 200 200 200 200 200 200 200 199 224 224 224 224 224 224 224 224 224
 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 116 120 125 129 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 200 200 199 200 224 224 224 224 224 224 224 224 224 224 224


図1.3.1 PNMビューアで turtle.pgm を見る

1.4 PPM形式ファイル

次にカラー画像を表現するPPMファイルを見てみよう。 これもテキストファイルなので,テキストエディタで開いて内部を見ることができる。 ファイルturtle.ppmは非常に大きなファイルなので,File 1.4.1ではファイルの最初のごく一部を示している。
PNMビューアを用いてこのファイルを表示すると,図1.4.1のようにカラー画像となる。

ファイルturtle.ppmの最初の3行には「P3」「64」「64」「255」が書いてあるのが見える。
この部分もPNMヘッダである。
「P3」はこのファイルがカラー画像ASCII形式(テキスト形式)であることを表し,これも「マジックナンバー」である。 続く「64」「64」は横画素数(ピクセル数)が64で縦画素数も64であることを示している。
最後の「255」は,RGBの各画素値のとりうる値の最大値は255であることを示している。
この3行の後に続くのが64×64×3個の画素値である。ここでは,RGB(赤緑青・光の三原色)の3つの値一組で1つの画素を表している。
画像の1行目の最初は黄色の単色が並んでいるので,(241 237 51)がかたまりになって並んでいるのがわかる。

File 1.4.1 PPMファイル turtle.ppm の一部(最初の48画素分,4画素ごとに改行していることになる)

P3
64 64
255
241 237  51 241 237  51 241 237  51 241 237  51
241 237  51 241 237  51 241 237  51 241 237  51
241 237  51 241 237  51 241 237  51 241 237  51
241 237  51 241 237  51 241 237  51 241 237  51
241 237  51 241 237  51 241 237  51 241 237  51
241 237  51 241 237  51 241 237  51 241 237  51
241 237  51 241 237  51 241 237  51 241 237  51
241 237  51 241 237  51 241 237  51 241 237  51
241 237  51 241 237  51 241 237  51 241 237  51
241 237  51 241 237  51 241 237  51 241 237  51
241 237  51 241 237  51 241 237  51 241 237  51
241 237  51 241 237  51 241 237  51 241 237  51


図1.4.1 PNMビューアで turtle.ppm を見る

1.5 PNM形式

PNM形式に関するもう少し詳細な情報はこちらを参照してほしい。
次のようなことがわかる。
(1)ファイル中にコメントを埋め込むことができる(本Webテキストではコメントはないものとしている)
(2)P1形式の場合には,0,1しか出てこないので,画素値はスペースで区切らなくてもよい(本Webテキストでは扱わない)
(3)ヘッダ中の値の区切りや画素値の区切りは,もう少し柔軟である
(4)raw形式(バイナリ形式)でのフォーマットがあり,マジックナンバーはP4,P5,P6がある(本Webテキストでは扱わない)
PNMファイル例はこちらから取得できる


2.PNM形式ファイルの生成
2.1 PBM形式ファイルの生成

PNM形式ファイルの中で最初にPBM形式(マジックナンバーP1:白黒2値画像・テキスト形式)のファイルをCプログラムで生成してみよう。 File 1.2.1 のPBMファイルを作成したいところだが,プログラム中のデータが大きくなりすぎるので File 1.2.1 のPBMファイルの一部分(頭部のみ)を作成する。
List 2.1.1のpnmexample.cでは,生成したいファイルの内容を確認のために画面に表示し,その後にファイルを生成している。

画面への表示部分をある規則に従って修正すると,表示内容がそのままファイルに出力できるようになることに気づくであろう。
ファイルオープンの儀式(5行)とファイルクローズの儀式(1行)の間に,ファイル出力本体を画面表示のときと似たように記述している。
ファイル出力本体は, printf を fprintf に置き換え,かっこの中の先頭に fp を加えるだけである。


このプログラムを実行すると,turtlehead.pbmというファイルが生成される。
(pbm形式では,画素データと画素データの間には空白を入れない方法もあるが,常に空白を入れることにする)

List 2.1.1 亀の一部を表すPBMファイルを生成するC言語プログラム pnmexample.c

#include <stdio.h>
#include <stdlib.h> //関数exit()のために必要

#define SCREENSIZE 256

//変数の型unsigned charのことをbyte_tと表記するの意味
typedef unsigned char byte_t;

byte_t turtlePBMPixels[][SCREENSIZE] = {
    {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
    {0,0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
    {0,0,0,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
    {0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1},
    {0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1},
    {1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,1,1,1,1},
    {1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1},
    {1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,1,1,1,1,1,1},
    {0,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,1,1,1,1,1,1,1},
    {0,0,0,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1},
    {0,0,0,0,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1},
    {0,0,0,0,1,1,1,1,1,1,1,1,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1},
    {0,0,0,0,1,1,1,1,1,1,1,1,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1},
    {0,0,0,0,1,1,1,1,1,1,1,1,1,0,0,0,1,1,1,1,1,1,1,1,1,1,1},
    {0,0,0,0,1,1,1,1,1,1,1,1,1,0,0,0,1,1,1,1,1,1,1,1,1,1,1},
    {0,0,0,0,0,1,1,1,1,1,1,1,1,0,0,1,1,1,1,1,1,1,1,1,1,1,1},
    {0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
    {0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
    {0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,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,1,1,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,1,1,1,1,1,1,1,1,1,1,1,1,1},
    {0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
    {0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
    {0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
    {0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
    {0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
    {0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1},
    {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1},
    {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1},
    {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1}
};

int magicnumber=1;
int width=27;
int height=30;
char filename[]="turtlehead.pbm";

int main()
{
    FILE *fp;   //ファイル出力のために必要
    int row,column;

    //画面出力 ここから
    printf("P%d\n",magicnumber);
    printf("%d %d\n",width,height);
    for (row=0; row<height; row++) {
        for (column=0; column<width; column++) {
            printf(" %d",turtlePBMPixels[row][column]);
        }
        printf("\n");
    }
    //画面出力 ここまで

    //ファイル出力 ここから
    fp=fopen(filename,"w"); //書き込み(Write)用にファイルを開く
    if (fp==NULL) { //開けなかった時の処理
        printf("can't open file [%s]\n",filename);
        exit(1); //強制終了
    }
    fprintf(fp,"P%d\n",magicnumber);
    fprintf(fp,"%d %d\n",width,height);
    for (row=0; row<height; row++) {
        for (column=0; column<width; column++) {
            fprintf(fp," %d",turtlePBMPixels[row][column]);
        }
        fprintf(fp,"\n");
    }
    fclose(fp); //ファイルを閉じる
    //ファイル出力 ここまで

    return 0;
}

グローバル変数の使用についての補足

関数の外部に宣言されている変数はグローバル変数である。
大きなサイズの配列は,グローバル変数として宣言されることが多い。(スタック領域軽減のため)
画像のための2次元配列がグローバル変数として宣言されたため,それと密接な関係を持つ変数もグローバル変数として宣言されている。

typedef unsigned char byte_t; に関する補足  

Screen 2.1.1 実行結果(画面表示) これと同じ内容がファイル turtlehead.pbm に書き込まれる

P1
27 30
 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
 0 0 0 0 0 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
 0 0 0 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
 0 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1
 0 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1
 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 1 1 1 1
 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 1 1 1 1 1
 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 1 1 1 1 1 1
 0 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 1 1 1 1 1 1 1
 0 0 0 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1
 0 0 0 0 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1
 0 0 0 0 1 1 1 1 1 1 1 1 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1
 0 0 0 0 1 1 1 1 1 1 1 1 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1
 0 0 0 0 1 1 1 1 1 1 1 1 1 0 0 0 1 1 1 1 1 1 1 1 1 1 1
 0 0 0 0 1 1 1 1 1 1 1 1 1 0 0 0 1 1 1 1 1 1 1 1 1 1 1
 0 0 0 0 0 1 1 1 1 1 1 1 1 0 0 1 1 1 1 1 1 1 1 1 1 1 1
 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 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 1 1 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 1 1 1 1 1 1 1 1 1 1 1 1 1
 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1
 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1
 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1
 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1


図2.1.1 PNMビューアで turtlehead.pbm を見る

 課題1  List 2.1.1 における,PBMファイルイメージ画面表示機能, PBMファイル生成機能をそれぞれ関数化し List 2.1.2 のプログラムを作成する。 List 2.1.2 において,関数 generatePBMFile() ができていないので,List 2.1.1を参考に完成さなさい。実行結果は Screen 2.1.1 と同じになることが期待されている。 生成されたファイル turtlehead1.pbm の内容をコメントとしてプログラムソースファイルの末尾に貼り付けなさい。 (提出ファイル名 pnmprog1.c)

List 2.1.2 亀の一部を表すPBMファイルを生成するC言語プログラム(未完成)

#include <stdio.h>
#include <stdlib.h> //関数exit()のために必要

#define SCREENSIZE 256

typedef unsigned char byte_t;

/*****
 * PBMファイルイメージを画面表示する関数showPBMImage()
 * int magic マジックナンバーの数値の部分
 * intwidth 画像の横幅
 * int height 画像の高さ
 * byte_t pixelarea[][] 画素データ2次元配列
 * magic,width,height,pixelareaには矛盾が無いものとする
*****/
void showPBMImage(int magic, int width, int height, byte_t pixelarea[][SCREENSIZE])
{
    int row,column;
    printf("P%d\n",magic);
    printf("%d %d\n",width,height);
    for (row=0; row<height; row++) {
        for (column=0; column<width; column++) {
            printf(" %d",pixelarea[row][column]);
        }
        printf("\n");
    }
}

/*****
 * PBMファイルを生成する関数generatePBMFile()
 * char fname[] 生成するファイル名
 * int magic マジックナンバーの数値の部分
 * intwidth 画像の横幅
 * int height 画像の高さ
 * byte_t pixelarea[][] 画素データ2次元配列
 * magic,width,height,pixelareaには矛盾が無いものとする
 * 戻り値 ファイル生成に成功したら0,失敗したら-1
*****/
int generatePBMFile(char fname[], int magic, int width, int height, byte_t pixelarea[][SCREENSIZE])
{
    /*  ----
  
    この部分を完成させる
  
    ----  */
}

byte_t turtlePBMPixels[][SCREENSIZE] = {

 /*--- この部分は前に作成したものを使う ---*/

};

int magicnumber=1;
int width=27;
int height=30;
char filename[]="turtlehead1.pbm";

int main()
{
    int result;
    //画面出力
    showPBMImage(magicnumber,width,height,turtlePBMPixels);
    //ファイル出力
    result=generatePBMFile(filename,magicnumber,width,height,turtlePBMPixels);
    if (result==0) printf("[%s] completed\n",filename);
    else printf("[%s] failed\n",filename);
    return 0;
}

グローバル変数の使用についての補足

大きなサイズの配列は,グローバル変数として宣言されることが多い。
画像のための2次元配列がグローバル変数として宣言されたため,それと密接な関係を持つ変数もグローバル変数として宣言されている。
プログラム内で,すべての関数より前に宣言されたグローバル変数は,すべての関数内から見えていて,引数を使わなくても直接使えるようになる。例えば List 2.1.2 で byte_t turtlePBMPixels が関数 showPBMImage() より上に宣言されていたなら,引数に byte_t pixelarea がなくても,直接 turtlePBMPixels を使うことができるようになる。
List 2.1.2 のプログラムでは,グローバル変数はmainの直前に宣言されている。そのため,それより前に書かれている関数からは見えないため,関数は引数で値を受け 取るように作成される。このように必要な変数は引数として受け取るように関数を作っておくことで関数の独立性が確保され,他のプログラムへの使いまわし が可能になる。

実行時の画面の様子  

2.2 PNMヘッダに構造体を使う

PNMヘッダ内の値は,常に一緒に使われ,ばらばらになることはない。このような場合は構造体として扱うのが都合がよい。List 2.1.2を構造体を使って書き換えよう。

参考 構造体の復習 

 課題2   PNMヘッダの記述に構造体を取り入れ,List 2.1.2の2つの関数showPBMImage(),generatePBMFile()を書き換えて,List 2.2.1の関数showPBMImageV2(),generatePBMFileV2()としよう。List 2.2.1 の関数 generatePBMFileV2()を完成させて,プログラムを完成させなさい。 実行結果を確認し,生成されたファイルturtlehead2.pbmの内容をコメントとしてプログラムソースファイルの末尾に貼り付けなさい。 (提出ファイル名 pnmprog2.c)

List 2.2.1 亀の一部を表すPBMファイルを生成するC言語プログラム(未完成)

#include <stdio.h>
#include <stdlib.h> //関数exit()のために必要

#define SCREENSIZE 256

typedef unsigned char byte_t;
typedef struct {
    int magic;    //マジックナンバーの数値の部分
    int width;    //画像の横幅
    int height;   //画像の高さ
    int maxvalue; //画素値の最大値
} pnmheader_t;

/*****
 * PBMファイルイメージを画面表示する関数showPBMImageV2()
 * pnmheader_t pnmheader PNMヘッダ構造体
 * byte_t pixelarea[][] 画素データ2次元配列
 * PNMヘッダ構造体内のmagic,width,heightおよびpixelareaには矛盾が無いものとする
*****/
void showPBMImageV2( pnmheader_t pnmheader, byte_t pixelarea[][SCREENSIZE])
{
    int row,column;
    printf("P%d\n",pnmheader.magic);
    printf("%d %d\n",pnmheader.width,pnmheader.height);
    for (row=0; row<pnmheader.height; row++) {
        for (column=0; column<pnmheader.width; column++) {
            printf(" %d",pixelarea[row][column]);
        }
        printf("\n");
    }
}

/*****
 * PBMファイルを生成する関数generatePBMFileV2()
 * char fname[] 生成するファイル名
 * pnmheader_t pnmheader PNMヘッダ構造体
 * byte_t pixelarea[][] 画素データ2次元配列
 * PNMヘッダ構造体内のmagic,width,heightおよびpixelareaには矛盾が無いものとする
 * 戻り値 ファイル生成に成功したら0,失敗したら-1
*****/
int generatePBMFileV2(char fname[], pnmheader_t pnmheader, byte_t pixelarea[][SCREENSIZE])
{
    /*  ----
  
    この部分を完成させる
  
    ----  */
}

byte_t turtlePBMPixels[][SCREENSIZE] = {

 /*--- この部分は前に作成したものを使う ---*/

};

pnmheader_t turtlePBMheader= {
    1,    // magicの値
    27,   // widthの値
    30,   // heightの値
    1     // maxvalueの値
};
char filename[]="turtlehead2.pbm";

int main()
{
    int result;
    //画面出力
    showPBMImageV2(turtlePBMheader,turtlePBMPixels);
    //ファイル出力
    result=generatePBMFileV2(filename,turtlePBMheader,turtlePBMPixels);
    if (result==0) printf("[%s] completed\n",filename);
    else printf("[%s] failed\n",filename);
    return 0;
}

補足
pnmheader_t型構造体の要素に,int maxvalue;がある。
PBMを対象としたこのプログラムでは使わないが,PGM,PPMでも使えるようにこの要素が用意されている。

実行時の画面の様子  

2.3 PGM形式ファイルの生成とファイル生成関数の汎用化

List 2.2.1をもとに,PGM形式のファイルも扱うことが出来るように,画面表示関数,ファイル生成関数を改良しよう。
PGM形式ファイルではヘッダに画素値の最大値が含まれる。しかし,先に作成した構造体定義がすでにそのための要素を持っているので,そのまま流用可能である。 また画素値も0から255までの間の値をとるので,これまで通りbyte型の2次元配列が使える。
PGM形式のファイルでは3行目に「画素値の最大値」が書かれている。
PGM形式のファイルではマジックナンバーがP2なので,画面表示関数, ファイル生成関数は受け取ったPNMヘッダ中のマジックナンバーを見てこれが2であるならば3行目に「画素値の最大値」を書けば良いことになる。 画素値は画像を示す2次元配列に保存されている値を,空白で区切りながら出力すれば良い。 なお,PGM形式のファイルでは,値の桁数がふぞろいなのでファイル中の1行の長さが不揃いになる。

 課題3   List 2.2.1の2つの関数showPBMImageV2(),generatePBMFileV2()を,PBM/PGMのどちらにも使えるように書き換えて,List 2.3.1の関数showPBMPGMImage(),generatePBMPGMFile()としよう。generatePBMPGMFile()を完成させて,List 2.3.1のプログラムを完成させなさい。 実行結果を確認し,生成された2つのファイル(turtlehead3.pbmとturtlehead3.pgm)の内容をコメントとしてプログラムソースファイルの末尾に貼り付けなさい。 (提出ファイル名 pnmprog3.c)

List 2.3.1 亀の一部を表すPBMファイルとPGMファイルを生成するC言語プログラム(未完成)

#include <stdio.h>
#include <stdlib.h> //関数exit()のために必要

#define SCREENSIZE 256

typedef unsigned char byte_t;
typedef struct {
    int magic;    //マジックナンバーの数値の部分
    int width;    //画像の横幅
    int height;   //画像の高さ
    int maxvalue; //画素値の最大値
} pnmheader_t;

/*****
 * PBMおよびPGMファイルイメージを画面表示する関数showPBMPGMImage()
 * pnmheader_t pnmheader PNMヘッダ構造体
 * byte_t pixelarea[][] 画素データ2次元配列
 * PNMヘッダ構造体内のmagic,width,heightおよびpixelareaには矛盾が無いものとする
*****/
void showPBMPGMImage( pnmheader_t pnmheader, byte_t pixelarea[][SCREENSIZE])
{
    int row,column;
    printf("P%d\n",pnmheader.magic);
    printf("%d %d\n",pnmheader.width,pnmheader.height);
    if (pnmheader.magic==2) {
        printf("%d\n",pnmheader.maxvalue);
    }
    for (row=0; row<pnmheader.height; row++) {
        for (column=0; column<pnmheader.width; column++) {
            printf(" %d",pixelarea[row][column]);
        }
        printf("\n");
    }
}

/*****
 * PBMおよびPGMファイルを生成する関数generatePBMPGMFile()
 * char fname[] 生成するファイル名
 * pnmheader_t pnmheader PNMヘッダ構造体
 * byte_t pixelarea[][] 画素データ2次元配列
 * PNMヘッダ構造体内のmagic,width,heightおよびpixelareaには矛盾が無いものとする
 * 戻り値 ファイル生成に成功したら0,失敗したら-1
*****/
int generatePBMPGMFile(char fname[], pnmheader_t pnmheader, byte_t pixelarea[][SCREENSIZE])
{
    /*  ----
  
    この部分を完成させる
  
    ----  */
}

byte_t turtlePBMPixels[][SCREENSIZE] = {

 /*--- この部分は前に作成したものを使う ---*/

};

pnmheader_t turtlePBMheader= {

 /*--- この部分は前に作成したものを使う ---*/

};
char filename1[]="turtlehead3.pbm";

byte_t turtlePGMPixels[][SCREENSIZE] = {
    {224,224,224,224,224,224,224,224,224,224,224,224,224,224,224,224,224,224,224},
    {224,224,224, 63, 63, 64, 63, 63, 63, 63,224,224,224,224,224,224,224,224,224},
    {224,224, 64, 63, 63, 64, 63, 64, 63, 64, 63, 64,224,224,224,224,224,224,224},
    {224, 64, 63, 64, 63, 63, 63, 64, 63, 63, 64, 64,224,224,224,224,224,224,224},
    { 63, 64, 64, 64, 64, 63, 63, 63, 63, 63, 63, 63, 64,224,224,224,224,224,224},
    { 63, 63, 63, 63, 63, 64, 63, 63, 63, 64, 63, 63, 63,224,224,224,224,224,224},
    { 64, 63, 64, 63, 63, 63, 64, 63, 64, 64, 64, 63, 64,224,224,224,224,224,224},
    { 63, 63, 63, 64, 63, 64, 64, 64, 64, 63, 63, 64, 63,224,224,224,224,224,224},
    { 64, 64, 63, 64, 64, 63, 63, 64, 63, 64, 63, 63, 64,224,224,224,224,224,224},
    {224,224, 63, 63, 63, 63, 63, 63, 64, 63, 64, 64, 64,224,224,224,224,224,224},
    {224,224,224, 64, 64, 63, 63, 64, 64, 63, 63, 63, 64,224,224,224,224,224, 63},
    {224,224,224, 64, 64, 63, 63, 64, 63, 63, 64, 63, 64,224,224,224,224, 63, 63},
    {224,224,224,224, 63, 63, 63, 63, 64, 64, 64, 63, 63,224,224,224, 64, 64, 63},
    {224,224,224,224, 64, 64, 63, 64, 63, 64, 63, 63, 63,224,224,224, 64, 63, 64},
    {224,224,224,224, 64, 64, 64, 63, 63, 63, 64, 63, 63,224,224, 64, 63, 63, 63},
    {224,224,224,224, 64, 64, 64, 64, 64, 63, 63, 63, 63, 64, 64, 63, 64, 64, 64},
    {224,224,224,224, 63, 64, 63, 64, 64, 64, 63, 64, 64, 64, 64, 63, 64, 64, 64},
    {224,224,224,224,224, 63, 64, 64, 63, 64, 63, 64, 64, 63, 63, 64, 63, 64, 64},
    {224,224,224,224,224, 63, 64, 63, 63, 64, 63, 64, 64, 63, 63, 63, 64, 64, 64},
    {224,224,224,224,224, 64, 63, 63, 63, 63, 63, 64, 63, 64, 63, 64, 64, 64, 65},
    {224,224,224,224,224,224, 63, 63, 64, 63, 64, 64, 64, 63, 63, 63, 63, 64, 68},
    {224,224,224,224,224,224, 63, 63, 63, 63, 63, 64, 63, 63, 64, 64, 63, 67, 71},
    {224,224,224,224,224,224,224, 63, 63, 64, 64, 63, 64, 63, 63, 64, 66, 71, 74},
    {224,224,224,224,224,224,224,224, 63, 64, 63, 63, 63, 64, 64, 65, 69, 74, 78},
    {224,224,224,224,224,224,224,224,224, 63, 64, 64, 64, 64, 64, 68, 72, 76, 80},
    {224,224,224,224,224,224,224,224,224,224, 63, 64, 64, 64, 67, 72, 76, 79, 84},
    {224,224,224,224,224,224,224,224,224,224,224,224, 64, 66, 71, 75, 79, 83, 87},
    {224,224,224,224,224,224,224,224,224,224,224,224,224,224,224, 78, 82, 86, 91},
    {224,224,224,224,224,224,224,224,224,224,224,224,224,224,224,224,224, 89, 93},
    {224,224,224,224,224,224,224,224,224,224,224,224,224,224,224,224,224, 92, 97},
    {224,224,224,224,224,224,224,224,224,224,224,224,224,224,224,224, 91, 95, 99},
    {224,224,224,224,224,224,224,224,224,224,224,224,224,224,224, 90, 94, 99,102},
    {224,224,224,224,224,224,224,224,224,224,224,224,224,224, 89, 93, 98,102,106},
    {224,224,224,224,224,224,224,224,224,224,224,224,224,224, 92, 97,101,105,108},
    {224,224,224,224,224,224,224,224,224,224,224,224,224, 92, 96,100,104,108,112},
    {224,224,224,224,224,224,224,224,224,224,224,224,224, 94, 98,103,107,111,115},
    {224,224,224,224,224,224,224,224,224,224,224,224,224, 97,102,106,110,114,118},
    {224,224,224,224,224,224,224,224,224,224,224,224,224,224,105,108,113,117,121}
};

pnmheader_t turtlePGMheader= {
    2,    // magicの値
    19,   // widthの値
    38,   // heightの値
    255   // maxvalueの値
};
char filename2[]="turtlehead3.pgm";

int main()
{
    int result;
    //画面出力
    showPBMPGMImage(turtlePBMheader,turtlePBMPixels);
    //ファイル出力
    result=generatePBMPGMFile(filename1,turtlePBMheader,turtlePBMPixels);
    if (result==0) printf("[%s] completed\n",filename1);
    else printf("[%s] failed\n",filename1);
    //画面出力
    showPBMPGMImage(turtlePGMheader,turtlePGMPixels);
    //ファイル出力
    result=generatePBMPGMFile(filename2,turtlePGMheader,turtlePGMPixels);
    if (result==0) printf("[%s] completed\n",filename2);
    else printf("[%s] failed\n",filename2);
    return 0;
}

実行時の画面の様子  

3.ストライプ・チェッカー模様のPNMファイルを作る
3.1 準備 - 指定矩形領域のフィル

図3.2.1 から 図3.2.4,図3.3.1 から 図3.3.4のようなストライプ・チェッカー模様のPNMファイルを作成しよう。その前に有用な関数を作っておこう。List 3.1.1は2次元配列の指定矩形領域に同じ値を埋め込むC言語プログラムである。 ここでは,作業領域全体に指定した値を埋め込む関数 initialize2Darea(),作業領域内の指定した矩形領域に指定した値を埋め込む関数fill2Darea00(), 作業領域内の指定した矩形領域の「見える化」を行う関数show2Darea()がある。 mainではこの3つの関数に対して,適当な値を与えてテストしている。
関数show2Darea()では表示領域内のセルを必ず1桁で表示し,桁数による表示の乱れがないような工夫がしてある。 (表示範囲のセル内の値が9までの値だけなら必ず1桁で表示出来るが,そうでない場合でも1桁で表示できるようになっている。)

List 3.1.1 2次元配列の指定矩形領域に同じ値を埋め込む関数とそのテスト fillfunction.c

#include <stdio.h>

#define SCREENSIZE 256

//変数の型unsigned charのことをbyte_tと表記するの意味
typedef unsigned char byte_t;

/*****
 * SCREENSIZE×SCREENSIZEのbyte型2次元配列を指定する値で埋め,
 * 初期化する関数initialize2Darea()
 * byte_t pixelarea[][] SCREENSIZE×SCREENSIZEのchar型2次元配列
 * byte_t filler 埋めて欲しい値 ただし0から255までの値
*****/
void initialize2Darea(byte_t pixelarea[][SCREENSIZE],byte_t filler)
{
    int row,column;
    for (row=0; row<SCREENSIZE; row++) {
        for (column=0; column<SCREENSIZE; column++) {
            pixelarea[row][column]=filler;
        }
    }
}

/*****
 * byte型2次元配列の指定した矩形範囲を指定する値で埋める関数fill2Darea00()
 * ただし,矩形は左上隅のセルを1つの頂点とする矩形で,
 * 範囲の指定は,横幅と高さで与えられるものとする
 * byte_t pixelarea[][] SCREENSIZE×SCREENSIZEのchar型2次元配列
 * int width 矩形の横幅
 * int height 矩形の高さ
 * byte_t filler 埋めて欲しい値 ただし0から255までの値
 * 例えばwidth=4,height=6であったなら,左上のarea[0][0]を含む6*4=24個
 * のセルに指定値fillerが埋められる
*****/
void fill2Darea00(byte_t pixelarea[][SCREENSIZE], int width, int height, byte_t filler)
{
    int row,column;
    for (row=0; row<height; row++) {
        for (column=0; column<width; column++) {
            pixelarea[row][column]=filler;
        }
    }
}

/*****
 * byte型2次元配列の指定した矩形範囲を表示する関数show2Darea()
 * ただし,矩形は左上隅のセルを1つの頂点とする矩形で,
 * 範囲の指定は,横幅と高さで与えられるものとする
 * byte_t pixelarea[][] SCREENSIZE×SCREENSIZEのchar型2次元配列
 * int width 矩形の横幅
 * int height 矩形の高さ
 * byte_t max 領域内の最大値 ただし0から255までの値
 * 例えばwidth=4,height=6であったなら,左上のarea[0][0]を含む6*4=24個
 * のセルが表示される
 * 最大値が9以下だったら,セル内の値がそのまま表示されるが,
 * 9を越える値が指定されると9*pixelarea[i][j]/maxの値が表示され,
 * 1セルは1桁で表示されることが守られる
*****/
void show2Darea(byte_t pixelarea[][SCREENSIZE], int width, int height, byte_t max)
{
    int row,column;
    if (max<=9) {
        for (row=0; row<height; row++) {
            for (column=0; column<width; column++) {
                printf("%d",pixelarea[row][column]);
            }
            printf("\n");
        }
    } else {
        for (row=0; row<height; row++) {
            for (column=0; column<width; column++) {
                printf("%d",pixelarea[row][column]*9/max);
            }
            printf("\n");
        }
    }
}

byte_t pixelarea[SCREENSIZE][SCREENSIZE];

//テストとして,次の作業をしている
//作業領域全体に1を埋め込む
//作業領域の左上の幅5,高さ4の矩形領域に8を埋め込む
//作業領域の左上の幅8,高さ6の矩形領域を表示する
int main()
{
    initialize2Darea(pixelarea,1);
    fill2Darea00(pixelarea,5,4,8);
    show2Darea(pixelarea,8,6,0);
    return 0;
}

Screen 3.1.1 テスト実行結果

88888111
88888111
88888111
88888111
11111111
11111111

 課題4  List3.1.1の関数 において,関数 fill2Darea00() を修正して汎用性の高い関数 fill2Darea() を作ろう。
関数 fill2Darea00() は作業領域の左上のセルから矩形が始まり,右方向に width 個, 下方向へ height 個の矩形領域セルに指定した値を埋めるものであった。 関数 fill2Darea() は作業領域中の指定したセルから矩形が始まり, 右方向に width 個,下方向へ height 個の矩形領域セルに指定した値を埋める関数になる。List 3.1.2の関数 fill2Darea() を完成させプログラム全体を完成させなさい。実行結果を確認し,実行結果をコメントとしてプログラムソースファイルの末尾に貼り付けなさい。 (提出ファイル名 pnmprog4.c)

関数fill2Darea()仕様
与えられたbyte型2次元配列の指定した矩形範囲を,指定する値で埋める
矩形範囲は,左上頂点のセルの位置と矩形の幅と高さを与えることによって指定する
引数は次のとおりとする。
(1)byte_t pixelarea[][] SCREENSIZE×SCREENSIZEのchar型2次元配列
(2)int left 矩形の左端 (作業領域の左端から右方へ0,1,2...と数える)
(3)int top 矩形の上端 (作業領域の上端から下方へ0,1,2...と数える)
(4)int width 矩形の横幅
(5)int height 矩形の高さ
例えばleft=5,top=8,width=7,height=6であったなら,
左上:pixelarea[8][5],右上:pixelarea[8][11],左下:pixelarea[13][5],右下:pixelarea[13][11]
の矩形6*7=42個のセルにfillerの値が埋め込まれる

List 3.1.2 2次元配列の指定矩形領域に同じ値を埋め込む関数とそのテスト

#include <stdio.h>

#define SCREENSIZE 256

//変数の型unsigned charのことをbyte_tと表記するの意味
typedef unsigned char byte_t;

/*次の関数は前に作成した関数をそのままコピー&ペーストする
void initialize2Darea(byte_t pixelarea[][SCREENSIZE],byte_t filler)
void show2Darea(byte_t pixelarea[][SCREENSIZE], int width, int height, byte_t max)
*/

/*****
 * byte型2次元配列の指定した矩形範囲を指定する値で埋める関数fill2Darea()
 * 範囲の指定は,左上隅のセル位置と,横幅と高さで与えられるものとする
 * byte_t pixelarea[][] SCREENSIZE×SCREENSIZEのchar型2次元配列
 * int left 矩形の左端 (作業領域の左端から右方へ0,1,2...と数える)
 * int top 矩形の上端 (作業領域の上端から下方へ0,1,2...と数える)
 * int width 矩形の横幅
 * int height 矩形の高さ
 * byte_t filler 埋めて欲しい値 ただし0から255までの値
 * 例えばleft=5,top=8,width=4,height=6であったなら,pixelarea[8][5]を含む6*4=24個
 * のセルに指定値fillerが埋められる
*****/
void fill2Darea(byte_t pixelarea[][SCREENSIZE], int left, int top, int width, int height, byte_t filler)
{
    /*  ----
  
    この部分を完成させる
  
    ----  */
}

byte_t pixelarea[SCREENSIZE][SCREENSIZE];

//テストとして,次の作業をしている
//作業領域全体に1を埋め込む
//作業領域のセル[0][0]から幅5,高さ4の矩形領域に8を埋め込む
//作業領域のセル[2][3]から幅5,高さ4の矩形領域に2を埋め込む
// セルの指定は行,列の順であることに注意
//作業領域の左上の幅8,高さ6の矩形領域を表示する
int main()
{
    initialize2Darea(pixelarea,1);
    fill2Darea(pixelarea,0,0,5,4,8);
    fill2Darea(pixelarea,3,2,5,4,2);
    show2Darea(pixelarea,10,8,0);
    return 0;
}

Screen 3.1.2 テスト実行結果はこうなればOK

8888811111
8888811111
8882222211
8882222211
1112222211
1112222211
1111111111
1111111111

3.2 縦ストライプ模様のPBMファイルを作る

図3.2.1 から 図3.2.4 のような縦ストライプ模様のPBMファイルを作ろう。 出来上がってみれば,縦ストライプ模様であるが,分解して考えれば,縦長の黒い矩形と白い矩形を交互に埋めていけば良いことがわかる。 そこで,List 3.1.2 で作った関数fill2Darea()を使うことになる。 この関数は左上の画素の位置(left,top)を左上頂点とし,幅widthと高さheightとする矩形を考え,矩形内の画素に指定した値 fillerを埋める関数であった。 描く矩形の個数だけこの関数を呼び出せばよい。そのとき,黒の場合はfillerに1を白の場合はfillerを0に与えればよい。

図3.2.1 vstripe1.pbm 図3.2.2 vstripe2.pbm
図3.2.3 vstripe3.pbm 図3.2.4 vstripe4.pbm

図3.2.1 から 図3.2.4 のような縦ストライプ模様を描くための設定は次のようなものである。

ファイル名
縦縞1本の幅
width
縦縞1本の高さ
height
縦縞本数
nStripes
出来上がり画像の幅
width×nStripes
出来上がり画像の
高さ
vstripe1.pbm1025
5
5025
vstripe2.pbm625
7
42
25
vstripe3.pbm
3
30
13
39
30
vstripe4.pbm
10
30
4
40
30

まず,vstripe1.pbmを作成するプログラムをList 3.2.1 のように作ってみよう。
埋めるべき5つの矩形は,次の表のような値で関数 fill2Darea() を呼び出せばよい。
この考え方で作成したプログラムがList3.2.1である。実行するとファイルvstripe1.pbmが作成されるので,PNMビューアで確認しなさい。

矩形の番号
左端から0,1,2...
 0 
 1 
 2 
 ... 
i ... 
left0
10
20
...10×i ...
top
00
0
... 0
...
width
10
10
10
... 10
...
height
25
25
25
... 25
...
filler 1
0
1
... (i+1)%2 ...

List 3.2.1 vstripe1.pbmを作成するプログラム 文字の小さなところは以前作成した内容

#include <stdio.h>

#define SCREENSIZE 256

//変数の型unsigned charのことをbyte_tと表記するの意味
typedef unsigned char byte_t;

typedef struct {
    int magic;    //マジックナンバーの数値の部分
    int width;    //画像の横幅
    int height;   //画像の高さ
    int maxvalue; //画素値の最大値
} pnmheader_t;

/*****
 * SCREENSIZE×SCREENSIZEのbyte型2次元配列を指定する値で埋め,
 * 初期化する関数initialize2Darea()
 * byte_t pixelarea[][] SCREENSIZE×SCREENSIZEのchar型2次元配列
 * byte_t filler 埋めて欲しい値 ただし0から255までの値
*****/
void initialize2Darea(byte_t pixelarea[][SCREENSIZE],byte_t filler)
{
    int row,column;
    for (row=0; row<SCREENSIZE; row++) {
        for (column=0; column<SCREENSIZE; column++) {
            pixelarea[row][column]=filler;
        }
    }
}

/*****
 * byte型2次元配列の指定した矩形範囲を指定する値で埋める関数fill2Darea()
 * 範囲の指定は,左上隅のセル位置と,横幅と高さで与えられるものとする
 * byte_t pixelarea[][] SCREENSIZE×SCREENSIZEのchar型2次元配列
 * int left 矩形の左端 (作業領域の左端から右方へ0,1,2...と数える)
 * int top 矩形の上端 (作業領域の上端から下方へ0,1,2...と数える)
 * int width 矩形の横幅
 * int height 矩形の高さ
 * byte_t filler 埋めて欲しい値 ただし0から255までの値
 * 例えばleft=5,top=8,width=4,height=6であったなら,pixelarea[8][5]を含む6*4=24個
 * のセルに指定値fillerが埋められる
*****/
void fill2Darea(byte_t pixelarea[][SCREENSIZE], int left, int top, int width, int height, byte_t filler)
{
    int row,column;
    for (row=0; row<height; row++) {
        for (column=0; column<width; column++) {
            pixelarea[row+top][column+left]=filler;
        }
    }
}

/*****
 * PBMおよびPGMファイルイメージを画面表示する関数showPBMPGMImage()
 * pnmheader_t pnmheader PNMヘッダ構造体
 * byte_t pixelarea[][] 画素データ2次元配列
 * PNMヘッダ構造体内のmagic,width,heightおよびpixelareaには矛盾が無いものとする
*****/
void showPBMPGMImage( pnmheader_t pnmheader, byte_t pixelarea[][SCREENSIZE])
{
    int row,column;
    printf("P%d\n",pnmheader.magic);
    printf("%d %d\n",pnmheader.width,pnmheader.height);
    if (pnmheader.magic==2) {
        printf("%d\n",pnmheader.maxvalue);
    }
    for (row=0; row<pnmheader.height; row++) {
        for (column=0; column<pnmheader.width; column++) {
            printf(" %d",pixelarea[row][column]);
        }
        printf("\n");
    }
}

/*****
 * PBMおよびPGMファイルを生成する関数generatePBMPGMFile()
 * char fname[] 生成するファイル名
 * pnmheader_t pnmheader PNMヘッダ構造体
 * byte_t pixelarea[][] 画素データ2次元配列
 * PNMヘッダ構造体内のmagic,width,heightおよびpixelareaには矛盾が無いものとする
 * 戻り値 ファイル生成に成功したら0,失敗したら-1
*****/
int generatePBMPGMFile(char fname[], pnmheader_t pnmheader, byte_t pixelarea[][SCREENSIZE])
{
    FILE *fp;
    int row,column;
    fp=fopen(fname,"w"); //書き込み(Write)用にファイルを開く
    if (fp==NULL) { //開けなかった時の処理
        printf("can't open file [%s]\n",fname);
        return -1; //終了
    }
    fprintf(fp,"P%d\n",pnmheader.magic);
    fprintf(fp,"%d %d\n",pnmheader.width,pnmheader.height);
    if (pnmheader.magic==2) {
        fprintf(fp,"%d\n",pnmheader.maxvalue);
    }
    for (row=0; row<pnmheader.height; row++) {
        for (column=0; column<pnmheader.width; column++) {
            fprintf(fp," %d",pixelarea[row][column]);
        }
        fprintf(fp,"\n");
    }
    fclose(fp); //ファイルを閉じる
    return 0;
}


byte_t pixelarea[SCREENSIZE][SCREENSIZE];

int main()
{
    pnmheader_t header;
    char fname[]="vstripe1.pbm";
    int width,height,nStripes;
    int result;
    int ix;
    int filler;
    initialize2Darea(pixelarea,1);

    //画像生成
    width=10;
    height=25;
    nStripes=5;
    for (ix=0; ix<nStripes; ix++) {
        filler=(ix+1)%2;
        fill2Darea(pixelarea,width*ix,0,width,height,filler);
    }
    //ヘッダ生成
    header.magic=1;
    header.width=width*nStripes;
    header.height=height;

    showPBMPGMImage(header,pixelarea);
    result=generatePBMPGMFile(fname,header,pixelarea);
    if (result==0) printf("[%s] completed\n",fname);
    else printf("[%s] failed\n",fname);
    return 0;
}

実行時の画面の様子  

 課題5  List 3.2.1 のプログラムでは特定な縦ストライプ模様しか作れなかった。そこで,引数で模様を設定することのできる一般化した関数 makeVStripePattern() をList 3.2.2 に作成し,プログラムを完成しなさい。
List 3.2.2 において現在有効になっているmain()はプログラム開発時用である。 提出時には現在有効になっていない方のmain()(typedef以下)で動かすようにしなさい。 4つのファイルが同時に作成されるのでPNMビューアで確認しなさい。図3.2.1 から 図3.2.4 のような縦ストライプ模様ができていたら成功である。
実行結果(画面表示)をコメントとしてプログラムソースファイルの末尾に貼り付けなさい。 (提出ファイル名 execpr5c)

関数 makeVStripePattern() は構造体を返す関数である。「構造体を返す」に関しては次の補足を参考にしなさい。

構造体を返す関数に関する補足  

List 3.2.2 縦ストライプ模様のpbmファイルを作成するプログラム

#include <stdio.h>

#define SCREENSIZE 256

//変数の型unsigned charのことをbyte_tと表記するの意味
typedef unsigned char byte_t;

typedef struct {
    int magic;    //マジックナンバーの数値の部分
    int width;    //画像の横幅
    int height;   //画像の高さ
    int maxvalue; //画素値の最大値
} pnmheader_t;


/*次の関数は前に作成した関数をそのままコピー&ペーストする
void initialize2Darea(byte_t pixelarea[][SCREENSIZE],byte_t filler)
void fill2Darea(byte_t pixelarea[][SCREENSIZE], int left, int top, int width, int height, byte_t filler)
void showPBMPGMImage( pnmheader_t pnmheader, byte_t pixelarea[][SCREENSIZE])
int generatePBMPGMFile(char fname[], pnmheader_t pnmheader, byte_t pixelarea[][SCREENSIZE])
*/

/*****
 * 作業領域に,指定した幅,高さの縦縞を指定本数持つPBM画像データを作る関数makeVStripePattern()
 *    (make Vertical Stripe Pattern の意味)
 * byte_t pixelarea[][] 作業結果保存用のSCREENSIZE×SCREENSIZEのchar型2次元配列
 * areaの左上に指定の縦縞模様を作る
 * int width 一本の縦縞の横幅
 * int height 縦縞の高さ
 * int nStripes 縦縞の本数
 * 返す値は,pnmheader_t構造体で,そのままPBMファイル出力に使えるものとする
 * 出来上がる縦縞は左から黒から始まり黒,白,黒,白,・・の順となる
 *
 * 例えばwidth=10,height=40,nStripes=5であったなら,
 * 全体の矩形は幅50,高さ40で,その内部には幅10の縦縞が5本になる
 * そして,この縦縞は黒,白,黒,白,黒の順に並んでいるようになる
*****/
pnmheader_t makeVStripePattern(byte_t pixelarea[][SCREENSIZE], int width, int height, int nStripes)
{
    /*  ----
  
    この部分を完成させる
  
    ----  */
}

byte_t pixelarea[SCREENSIZE][SCREENSIZE];

int main()
{
    pnmheader_t header;
    char fname[]="vstripe1.pbm";
    int width,height,nStripes;
    int result;
    initialize2Darea(pixelarea,1);
    width=10;
    height=25;
    nStripes=5;
    header=makeVStripePattern(pixelarea,width,height,nStripes);
    showPBMPGMImage(header,pixelarea);
    result=generatePBMPGMFile(fname,header,pixelarea);
    if (result==0) printf("[%s] completed\n",fname);
    else printf("[%s] failed\n",fname);
    return 0;
}

/* 提出時は上にあるmain()をコメントアウトして,ここより下を有効にしなさい
typedef struct {
    int width;    //縦縞パターン中の縦縞1本の幅
    int height;   //縦縞パターンの高さ
    int nStripes; //縦縞パターン中の縦縞本数
    char *fname;  //保存するファイル名(ポインタ)
} setting_t;

setting_t settings[] = {
    {10, 25, 5,"vstripe1.pbm"},
    { 6, 25, 7,"vstripe2.pbm"},
    { 3, 30,13,"vstripe3.pbm"},
    {10, 30, 4,"vstripe4.pbm"}
    //|   |  |  |
    //|   |  |  fname(文字列は先頭アドレスを表すのでOK)
    //|   |  nStripes
    //|   height
    //width
};

int main()
{
    pnmheader_t header;
    int width,height,nStripes;
    int result;
    int nTests=sizeof(settings)/sizeof(setting_t);
    int itest;//テスト番号
    for (itest=0; itest<nTests; itest++) {
        initialize2Darea(pixelarea,1);
        width=settings[itest].width;
        height=settings[itest].height;
        nStripes=settings[itest].nStripes;
        header=makeVStripePattern(pixelarea,width,height,nStripes);
        showPBMPGMImage(header,pixelarea);
        result=generatePBMPGMFile(settings[itest].fname,header,pixelarea);
        if (result==0) printf("[%s] completed\n",settings[itest].fname);
        else printf("[%s] failed\n",settings[itest].fname);
    }
    return 0;
}
*/

3.3 チェッカー模様のPBMファイルを作る

図3.3.1 から 図3.3.4 のようなチェッカー模様のPBMファイルを作ろう。 出来上がってみれば,チェッカー模様であるが,分解して考えれば, 黒い正方形と白い正方形を交互に埋めていけば良いことがわかる。 ここでも,List3.2.1,List3.2.2と同様にList 3.1.2 で作った関数fill2Darea()を使うことになる。

図3.3.1 checker1.pbm図3.3.2 checker2.pbm
図3.3.3 checker3.pbm図3.3.4 checker4.pbm

図3.3.1 から 図3.3.4 のようなチェッカー模様を描くための設定は次のようなものである。

ファイル名
正方形の一辺
sidelength
横に並ぶ正方形
の数
nSquaresH
縦に並ぶ正方形
の数
nSquaresV
出来上がり画像の幅
sidelength
×nSquaresH
出来上がり画像の高さ
sidelength
×nSquaresV
checker1.pbm84
5
32
40
checker2.pbm310
7
30
21
checker3.pbm
2
15
8
30
16
checker4.pbm
10
3
4
30
40

 課題6  List 3.3.1 はチェッカー模様のPBMファイルを作成プログラムである。関数 makeCheckerPattern() を作成し,プログラムを完成しなさい。 4つのファイルが同時に作成されるのでPNMビューアで確認しなさい。図3.3.1 から 図3.3.4 のようなチェッカー模様ができたら成功である。
実行結果(画面表示)をコメントとしてプログラムソースファイルの末尾に貼り付けなさい。 (提出ファイル名 execpr6c)

List 3.3.1 チェッカー模様のpbmファイルを作成するプログラム

#include <stdio.h>

#define SCREENSIZE 256

//変数の型unsigned charのことをbyte_tと表記するの意味
typedef unsigned char byte_t;

typedef struct {
    int magic;    //マジックナンバーの数値の部分
    int width;    //画像の横幅
    int height;   //画像の高さ
    int maxvalue; //画素値の最大値
} pnmheader_t;


/*次の関数は前に作成した関数をそのままコピー&ペーストする
void initialize2Darea(byte_t pixelarea[][SCREENSIZE],byte_t filler)
void fill2Darea(byte_t pixelarea[][SCREENSIZE], int left, int top, int width, int height, byte_t filler)
void showPBMPGMImage( pnmheader_t pnmheader, byte_t pixelarea[][SCREENSIZE])
int generatePBMPGMFile(char fname[], pnmheader_t pnmheader, byte_t pixelarea[][SCREENSIZE])
*/

/*****
 * 指定した一片の長さを持つ正方形を縦横に指定した数だけ並べてチェッカーパターンを
 * 作る関数makeCheckerPattern()
 * byte_t pixelarea[][] 作業結果保存用のSCREENSIZE×SCREENSIZEのchar型2次元配列
 * int sidelength 並べる正方形の一辺の長さ
 * int nSquaresH 横方向に並べる正方形の数(the number of squares along horizontal line)
 * int nSquaresV 縦方向に並べる正方形の数(the number of squares along vertical line)
 * 返す値は,pnmheader_t構造体で,そのままPBMファイル出力に使える
 * 出来上がる正方形の色は左上から黒,白,黒,白,・・・
 *                白,黒,白,黒,・・・
 *                黒,白,黒,白,・・・となる
 *                : : : : ・・・となる
 * 例えばsidelength=8,nSquaresH=5,nSquaresV=7であったなら,
 * 全体の矩形は幅40,高さ56で,その内部には一辺の長さが8の正方形が横に5個,縦に7個並んでいる
 * そして,この正方形の色は左上が黒で,右方向へも下方向へも白,黒,白,黒の順に並んでいる
*****/
pnmheader_t makeCheckerPattern(byte_t pixelarea[][SCREENSIZE], int sidelength, int nSquaresH, int nSquaresV)
{
    /*  ----
  
    この部分を完成させる
  
    ----  */
}

byte_t pixelarea[SCREENSIZE][SCREENSIZE];

typedef struct {
    int sidelength;//チェッカーパターン中の正方形の一辺の長さ
    int nSquaresH; //横方向に並べる正方形の数
    int nSquaresV; //縦方向に並べる正方形の数
    char *fname;   //保存するファイル名(ポインタ)
} setting_t;

setting_t settings[] = {
    { 8,  4, 5,"checker1.pbm"},
    { 3, 10, 7,"checker2.pbm"},
    { 2, 15, 8,"checker3.pbm"},
    {10,  3, 4,"checker4.pbm"}
    //|   |  |  |
    //|   |  |  fname(文字列は先頭アドレスを表すのでOK)
    //|   |  nSquaresV
    //|   nSquaresH
    //sidelength
};

int main()
{
    pnmheader_t header;
    int sidelength,nSquaresH,nSquaresV;
    int result;
    int nTests=sizeof(settings)/sizeof(setting_t);
    int itest;//テスト番号
    for (itest=0; itest<nTests; itest++) {
        initialize2Darea(pixelarea,1);
        sidelength=settings[itest].sidelength;
        nSquaresH=settings[itest].nSquaresH;
        nSquaresV=settings[itest].nSquaresV;
        header=makeCheckerPattern(pixelarea,sidelength,nSquaresH,nSquaresV);
        showPBMPGMImage(header,pixelarea);
        result=generatePBMPGMFile(settings[itest].fname,header,pixelarea);
        if (result==0) printf("[%s] completed\n",settings[itest].fname);
        else printf("[%s] failed\n",settings[itest].fname);
    }
    return 0;
}

3.4 グラデーション縦ストライプ模様のPGMファイルを作る

PGM形式では中間調のある白黒画像が作れるので,グラデーションのある縦ストライプ模様が作れる。
図3.4.1 から 図3.4.4 のようなグラデーション縦ストライプ模様のPBMファイルを作ろう。 これも分解して考えれば,明るさを徐々に変化させた縦長の矩形を埋めていけば良いことがわかる。 そこで,List 3.1.2 で作った関数fill2Darea()を使うことになる。 これまでPBMファイルの埋め込み値fillerには0または1のみを使ってきたが,この関数の 埋め込み値fillerは0から255までの値が使用できる。 この関数に左上の画素の位置(left,top)を左上頂点,幅widthと高さ heightを与え,矩形内の画素に徐々に変化する値fillerを与えれば良いことになる。
関数 makeGVStripePattern() はfillerに0から255までの値をできるだけ等間隔に変化させた値を計算している。 (fillerは整数なので小数点以下を四捨五入)

図3.4.1 gstripe1.pgm図3.4.2 gstripe2.pgm
図3.4.3 gstripe3.pgm 図3.4.4 gstripe4.pgm

 課題7  List 3.4.1 はグラデーション縦ストライプ模様のPGMファイルを作るプログラムである。すでに関数 makeGVStripePattern() は出来上がった状態になっている。 必要な関数をコピー&ペーストしてプログラムを完成しなさい。 4つのファイルが同時に作成されるのでPNMビューアで確認しなさい。図3.4.1 から 図3.4.4 のようなグラデーション縦縞模様ができたら成功である。
実行結果(画面表示)をコメントとしてプログラムソースファイルの末尾に貼り付けなさい。 (提出ファイル名 execpr7c)

List 3.4.1 グラデーション縦ストライプ模様のpgmファイルを作成するプログラム

#include <stdio.h>

#define SCREENSIZE 256

//変数の型unsigned charのことをbyte_tと表記するの意味
typedef unsigned char byte_t;

typedef struct {
    int magic;    //マジックナンバーの数値の部分
    int width;    //画像の横幅
    int height;   //画像の高さ
    int maxvalue; //画素値の最大値
} pnmheader_t;


/* 必要な関数をそのままコピー&ペーストする */

/*****
 * 作業領域に,指定した幅,高さの縦縞を指定本数持つPGM画像データを作る関数makeGVStripePattern()
 *    (make Gradation Vertical Stripe Pattern の意味)
 * byte_t pixelarea[][] 作業結果保存用のSCREENSIZE×SCREENSIZEのchar型2次元配列
 * areaの左上に指定の縦縞模様を作る
 * int width 一本の縦縞の横幅
 * int height 縦縞の高さ
 * int nStripes 縦縞の本数
 * 返す値は,pnmheader_t構造体で,そのままPBMファイル出力に使えるものとする
 * 出来上がる縦縞は左から黒から始まり順に明るくなり白で終わる
 *
 * 例えばwidth=10,height=40,nStripes=5であったなら,
 * 全体の矩形は幅50,高さ40で,その内部には幅10の縦縞が5本になる
 * そして,この縦縞は黒から順に明るくなって白で終わる
*****/
pnmheader_t makeGVStripePattern(byte_t pixelarea[][SCREENSIZE], int width, int height, int nStripes)
{
    pnmheader_t header;
    int ix;
    int filler;
    for (ix=0; ix<nStripes; ix++) {
        if (nStripes==1) {
            filler=0;
        } else {
            filler=(ix*255+(nStripes-1)/2)/(nStripes-1);
        }
        fill2Darea(pixelarea,width*ix,0,width,height,filler);
    }
    header.magic=2;   //ここが2になっているのでP2すなわちPGMファイルのヘッダである
    header.width=width*nStripes;
    header.height=height;
    header.maxvalue=255;//P2のファイルにはこれが必要
    return header;
}

byte_t pixelarea[SCREENSIZE][SCREENSIZE];

typedef struct {
    int width;    //縦縞パターン中の縦縞1本の幅
    int height;   //縦縞パターンの高さ
    int nStripes; //縦縞パターン中の縦縞本数
    char *fname;  //保存するファイル名(ポインタ)
} setting_t;

setting_t settings[] = {
    {10, 25, 5,"gstripe1.pbm"},
    { 6, 25, 7,"gstripe2.pbm"},
    { 3, 30,13,"gstripe3.pbm"},
    {10, 30, 4,"gstripe4.pbm"}
    //|   |  |  |
    //|   |  |  fname(文字列は先頭アドレスを表すのでOK)
    //|   |  nStripes
    //|   height
    //width
};

int main()
{
    pnmheader_t header;
    int width,height,nStripes;
    int result;
    int nTests=sizeof(settings)/sizeof(setting_t);
    int itest;//テスト番号
    for (itest=0; itest<nTests; itest++) {
        initialize2Darea(pixelarea,1);
        width=settings[itest].width;
        height=settings[itest].height;
        nStripes=settings[itest].nStripes;
        header=makeGVStripePattern(pixelarea,width,height,nStripes);
        showPBMPGMImage(header,pixelarea);
        result=generatePBMPGMFile(settings[itest].fname,header,pixelarea);
        if (result==0) printf("[%s] completed\n",settings[itest].fname);
        else printf("[%s] failed\n",settings[itest].fname);
    }
    return 0;
}

4.PNMファイルの読み込みと,画像処理
4.1 PBMファイルの読み込み

PBMファイルを読み込んで,正しく読み込めたことを確認したい。
PBMファイルにはコメントが埋め込まれている場合がある(こちらを参照)が,そのことは無視して, コメントが埋め込まれていないファイルだけを扱うことにする。 (コメントに対する読み込み時の対処は,それだけで大きな課題になるが,ここでは扱わない。)
最初にscanfについて確認しよう。 List 4.1.1とList 4.1.2はscanfを異なった形で書いた簡単なプログラムである。
PBMのヘッダと同じ内容をキーボードから読み込むことを考えると,List 4.1.1のプログラムでScreen 4.1.1の最初の入力方法が考えられるであろう。 しかし,scanfはかなり柔軟で,区切り文字で区切られていれば,どのように区切られていても正しく読むことが, 残りの4回に入力でわかるであろう。(区切り文字とは,スペースやタブや[ENTER]のことである) またList 4.1.2のような手抜きscanfでも柔軟に正しく読み込んでくれることがわかるであろう。 もっといろいろいじめてみなさい。
この柔軟性は,ファイルからの読み込みで使うfscanfについても当てはまることが期待できる。

List 4.1.1 scanfをテストするC言語プログラム scanf1.c

#include <stdio.h>

int main()
{
    char ch;
    int magic,width,height;
    scanf("%c%d",&ch,&magic);
    scanf("%d%d",&width,&height);
    printf("ch,magic,width,height=%c %d %d %d\n",ch,magic,width,height);
    return 0;
}

Screen 4.1.1 scanf1.exe 実行の様子 4回の異なる入力方法を試している
 わかりやすくするために,enterキーを押したところに[ENTER]と記述している
 [ENTER]がある行がキーボードから入力した行である

実行1
P1[ENTER]
100 200[ENTER]
ch,magic,width,height=P 1 100 200

実行2
P1 100 200[ENTER]
ch,magic,width,height=P 1 100 200

実行3
P[ENTER]
1[ENTER]
100[ENTER]
200[ENTER]
ch,magic,width,height=P 1 100 200

実行4
P      1     100    200[ENTER]
ch,magic,width,height=P 1 100 200

実行5
P1[ENTER]
[ENTER]
100[ENTER]
[ENTER]
200[ENTER]
ch,magic,width,height=P 1 100 200

List 4.1.2 scanfをテストするC言語プログラム scanf2.c

#include <stdio.h>

int main()
{
    char ch;
    int magic,width,height;
    scanf("%c%d%d%d",&ch,&magic,&width,&height);
    printf("ch,magic,width,height=%c %d %d %d\n",ch,magic,width,height);
    return 0;
}

Screen 4.1.2 scanf2.exe 実行の様子 4回の異なる入力方法を試している
 わかりやすくするために,enterキーを押したところに[ENTER]と記述している
 [ENTER]がある行がキーボードから入力した行である

実行1
P1[ENTER]
100 200[ENTER]
ch,magic,width,height=P 1 100 200

実行2
P1 100 200[ENTER]
ch,magic,width,height=P 1 100 200

実行3
P[ENTER]
1[ENTER]
100[ENTER]
200[ENTER]
ch,magic,width,height=P 1 100 200

実行4
P      1     100    200[ENTER]
ch,magic,width,height=P 1 100 200

実行5
P1[ENTER]
[ENTER]
100[ENTER]
[ENTER]
200[ENTER]
ch,magic,width,height=P 1 100 200

さて,画面表示と,ファイルへの出力が似ていることをList 2.1.1で確認した。 キーボード入力とファイルからの読み込みも同様である。

ファイルオープンの儀式(5行)とファイルクローズの儀式(1行)の間に,ファイル入力本体をキーボード入力のときと似たように記述している。
ファイル出力本体は, scanf を fscanf に置き換え,かっこの中の先頭に fp を加えるだけである。

List 4.1.3 ではファイル turtle.pbm のヘッダ部分だけを読み出している。 List 4.1.1と比較して欲しい。
turtle.pbm が実在すれば,List 4.1.3を実行すると,Screen 4.1.3が得られる。

List 4.1.3 fscanfによるPBMファイルからのPBMヘッダ部を読み込みをテストする fscanf.c

#include <stdio.h>
#include <stdlib.h> //関数exit()のために必要

int main()
{
    char fname[]="turtle.pbm";
    FILE *fp;
    char ch;
    int magic,width,height;
    fp=fopen(fname,"r"); //読み出し(read)用にファイルを開く
    if (fp==NULL) {//開けなかった時の処理
        printf("can't open file [%s]\n",fname);
        exit(1); //強制終了
    }
    fscanf(fp,"%c%d",&ch,&magic);
    fscanf(fp,"%d%d",&width,&height);
    fclose(fp); //ファイルを閉じる
    
    printf("ch,magic,width,height=%c %d %d %d\n",ch,magic,width,height);
    return 0;
}

Screen 4.1.3 fscanf.exe 実行の様子

ch,magic,width,height=P 1 64 39

4.2 PBMファイル,PGMファイルの読み込み

List 4.1.3 ではヘッダ部分のみ読み込んだが,ファイルを読み込んで読み込んだ内容を表示してみよう。 さらに,PBMとPGMの2つの形式に対応できるようにする。 (P2の場合はmaxvalueがヘッダに含まれているため対応することが必要である。)表示には関数 showPBMPGMImage() を使う。

 課題8  List 4.2.1 はファイル読み込みを関数 readPBMPGMFile() 内で行い,汎用性を高めようとしている。
関数 readPBMPGMFile() は作成途中なのでこれを作成してプログラムを完成しなさい。 一般に関数内では仕様以外の画面表示はしないため,ファイルが開けないなどの異常事態は,何らかの方法で呼び出し側に知らせ,対処してもらうようにしなければならない。 ここでは関数が返す構造体内のmagicに異常事態を示す値を載せて呼び出し側に返す仕様にしている。
実行結果(画面表示)をコメントとしてプログラムソースファイルの末尾に貼り付けなさい。 (提出ファイル名 execpr8c)

関数readPBMPGMFile()仕様
与えられたPBMまたはPGMファイル名にもとづいてヘッダおよび画素値を読み込む
・読み込んだヘッダ情報はヘッダ構造体を関数の返す値として,呼び出し側に返す
・読み込んだ画素値は,引数に与えられた2次元配列に取り込み,呼び出し側に返す
・ファイルオープンに失敗した場合はヘッダ構造体のマジックナンバ部に-1を載せて,呼び出し側に返す
引数は次のとおりとする。
(1)char fname[] 読み込むファイルのファイル名
(2)byte_t pixelarea[][] 画素データ2次元配列(読み込んだ画素値をここに保存すして返す)

List 4.2.1 PBMファイル,PGMファイルからの読み込み

#include <stdio.h>

#define SCREENSIZE 256

typedef unsigned char byte_t;
typedef struct {
    int magic;    //マジックナンバーの数値の部分
    int width;    //画像の横幅
    int height;   //画像の高さ
    int maxvalue; //画素値の最大値
} pnmheader_t;

/*****
 * PBMおよびPGMファイルイメージを画面表示する関数showPBMPGMImage()
 * pnmheader_t pnmheader PNMヘッダ構造体
 * byte_t pixelarea[][] 画素データ2次元配列
 * PNMヘッダ構造体内のmagic,width,heightおよびpixelareaには矛盾が無いものとする
*****/
void showPBMPGMImage( pnmheader_t pnmheader, byte_t pixelarea[][SCREENSIZE])
{
    int row,column;
    printf("P%d\n",pnmheader.magic);
    printf("%d %d\n",pnmheader.width,pnmheader.height);
    if (pnmheader.magic==2) {
        printf("%d\n",pnmheader.maxvalue);
    }
    for (row=0; row<pnmheader.height; row++) {
        for (column=0; column<pnmheader.width; column++) {
            printf(" %d",pixelarea[row][column]);
        }
        printf("\n");
    }
}

/*****
 * PBMまたはPGMファイルを読み込み,画素データ2次元配列に保存する関数readPBMPGMFile()
 * char fname[] 読み込むファイルのファイル名
 * byte_t pixelarea[][] 画素データ2次元配列(読み込んだ画素値をここに保存すして返す)
 * 関数の返す値は,読み込めたPNMファイルのヘッダを表すpnmheader_t構造体
 * ただし,ファイルのオープンに失敗した場合は,pnmheader_t内のmagicに-1を入れて返す
*****/
pnmheader_t readPBMPGMFile(char fname[], byte_t pixelarea[][SCREENSIZE])
{
    pnmheader_t header;

    /*  ----

    この部分を完成させる

    ヘッダ部を読み込む
    マジックナンバーが2だったら,maxvalueも読み込む
    ヘッダ情報で,画素値のデータサイズがわかるので,
    画素値データを2次元配列pixelareaに読み込む
    返すためのpnmheader_t headerをつくる
    その際,マジックナンバーが2だったら,maxvalueもheaderに入れることを忘れずに

    ----  */

    return header;
}

byte_t pixelarea[SCREENSIZE][SCREENSIZE];
pnmheader_t header;

int main()
{
    char *fname[]={
        "turtle.pbm", //PBMファイルとして正常に読み込めるはず
        "turtle.pgm", //PGMファイルとして正常に読み込めるはず
        "turtle.ppm", //PPMファイルなのでマジックナンバーP3 失敗するはず
        "abcdef.xyz", //存在しないファイルなので失敗するはず
    };
    int nTests=sizeof(fname)/sizeof(char *);
    int test;
    for(test=0; test<nTests; test++) {
        header=readPBMPGMFile(fname[test],pixelarea);
        if (header.magic==-1) {
            printf("can't open file [%s]\n",fname[test]);
        } else if (header.magic==1 || header.magic==2) {
            showPBMPGMImage(header,pixelarea);
            printf("[%s] completed\n",fname[test]);
        } else {
            printf("[%s] failed\n",fname[test]);
        }
    }
    return 0;
}

実行時の画面の様子  

4.3 PBMファイル,PGMファイルを読み込んで,白黒反転処理してファイルに書き出す

PBMファイル,PGMファイルは白黒モノラル画像である。白黒を反転してファイル出力しよう。図4.3.1 から図4.3.4に目標とする白黒反転の例がある。
PBMファイル,PGMファイルにおいて,白黒反転とは次のような作業になる。
PBMファイルの場合は画素値は0,1の2値しかないので,すべての画素値について,0なら1に,1なら0に変更すればよい。PGMファイルの場合は画素 値は0から255までの値なのですべての画素値について,0なら255に,1なら254に,1なら254に,2なら253に,3なら252に,4なら 251に,・・・254なら2に,255なら0に変更すればよい。
プログラム上は,入力用のbyte型2次元配列の有効画素を上記の変換を行って出力用のbyte型2次元配列を作成する関数を作ることで実現できる。ファイルの読み込みやファイルの出力はすでに作成した関数を用いればよい。

 課題9  List 4.3.1 はPBMファイル,PGMファイルを読み込んで,白黒反転処理してファイルに書き出すプログラムである。白黒反転関数 reverseBlackWhite() は作成途中なのでこれを作成してプログラムを完成しなさい。 2つのファイルが同時に作成されるのでPNMビューアで確認しなさい。  図4.3.2 と 図4.3.4 のような反転画像ができたら成功である。
実行結果(画面表示)をコメントとしてプログラムソースファイルの末尾に貼り付けなさい。 (提出ファイル名 execpr9c)

List 4.3.1 PBMファイル,PGMファイルを読み込んで,白黒反転処理してファイル出力

#include <stdio.h>
#include <string.h> //strcpy()のために必要

#define SCREENSIZE 256

typedef unsigned char byte_t;
typedef struct {
    int magic;    //マジックナンバーの数値の部分
    int width;    //画像の横幅
    int height;   //画像の高さ
    int maxvalue; //画素値の最大値
} pnmheader_t;

/* 必要な関数はここにコピー&ペーストする */

/*****
 * byte型2次元配列の画素値を白黒反転する関数reverseBlackWhite()
 * pnmheader_t pnmheader PNMヘッダ構造体
 *   これから,画素がPBMのものかPGMのものか区別がつく
 *   また有効画素の範囲もわかる
 * byte_t pixelareaIP[][] 画素データ2次元配列(入力,参照元)
 * byte_t pixelareaOP[][] 画素データ2次元配列(出力,参照元を変換して作成)
 * 戻り値 作業に成功したら0,失敗したら-1
 *         作業に失敗とは,pnmheader内のマジックナンバーが1,2以外
*****/
int reverseBlackWhite(pnmheader_t pnmheader,byte_t pixelareaIP[][SCREENSIZE],byte_t pixelareaOP[][SCREENSIZE])
{
    //必要な変数を宣言する

    if (pnmheader.magic==1) {

        /*  ----
        この部分を完成させる
        ----  */

    } else if (pnmheader.magic==2) {

        /*  ----
        この部分を完成させる
        ----  */

    } else {  //作業不能
        return -1;
    }
    return 0;
}

byte_t pixelareaIP[SCREENSIZE][SCREENSIZE];
byte_t pixelareaOP[SCREENSIZE][SCREENSIZE];
pnmheader_t header;

typedef struct {
    char *fnamein;
    char *fnameout;
} testfname_t;

testfname_t names[] ={
    {"turtle.pbm","turtlerev.pbm"}, //PBMファイルとして正常に読み込めるはず
    {"turtle.pgm","turtlerev.pgm"}, //PGMファイルとして正常に読み込めるはず
    {"turtle.ppm","turtlerev.ppm"}, //PPMファイルなのでマジックナンバーP3 失敗するはず
    {"abcdef.xyz","abcdefrev.xyz"}, //存在しないファイルなので失敗するはず
//    |            |
//    |            fnameout(出力用ファイル名)
//    fnamein(入力用ファイル名)
};

int main()
{
    char fnameIP[128];//入力用ファイル名
    char fnameOP[128];//出力用ファイル名
    int nTests=sizeof(names)/sizeof(testfname_t);
    int result;
    int test;
    for (test=0; test<nTests; test++) {
        strcpy(fnameIP,names[test].fnamein);// names[test].fnameinをfnameIPにコピー
        strcpy(fnameOP,names[test].fnameout);// names[test].fnameoutをfnameOPにコピー
        header=readPBMPGMFile(fnameIP,pixelareaIP);
        if (header.magic==-1) {
            printf("can't open file [%s]\n",fnameIP);
            continue; //ループの1回分はここで終了したとみなしなさいという命令
        }
        result=reverseBlackWhite(header,pixelareaIP,pixelareaOP);
        if (result==-1) {
            printf("[%s] failed reverseBlackWhite\n",fnameIP);
            continue;
        }
        //showPBMPGMImage(header,pixelareaOP);
        result=generatePBMPGMFile(fnameOP,header,pixelareaOP);
        if (result==-1) {
            printf("[%s] failed generate file\n",fnameOP);
            continue;
        }
        printf("[%s][%s] completed\n",fnameIP,fnameOP);
    }
    return 0;
}

Screen 4.3.1 実行結果の予想
本Webテキストの最初にダウンロードしたturtle.pbm,turtle.pgm,turtle.ppmが存在し,abcdef.xyzは存在していない場合

[turtle.pbm][turtlerev.pbm] completed
[turtle.pgm][turtlerev.pgm] completed
[turtle.ppm] failed reverseBlackWhite
can't open file [abcdef.xyz]

図4.3.1 turtle.pbm オリジナル図4.3.2 turtlerev.pbm 白黒反転
図4.3.3 turtle.pgm オリジナル図4.3.4 turtlerev.pgm 白黒反転

4.4 PBMファイル,PGMファイルを読み込んで,輪郭抽出してファイルに書き出す

輪郭抽出の例を図4.4.1から図4.4.4に示す。輪郭抽出は画素の値が急に変化するところを見つけて,そこに点を打 つことで実現する。いろいろな輪郭抽出方法があるが,いずれも,ある点の画素値を,その点とその点の周囲の点の画素値の差を計算している。List 4.3.1 と同様に入力されたbyte2次元配列から出力用のbyte2次元配列を作ることになる。
簡単なのはpixout[y][x]の値をpixin[y][x]-pixelareaIP[y-1][x]とpixin[y][x]-pixelareaIP[y][x-1]のように隣り合う画素値の差を計算して,2つの差をうまく1つの値にすればよい。
プログラム上はList 4.3.1のmainで関数 reverseBlackWhite() を呼び出す代わりに,関数 generatePBMPGMFile() を呼び出すようにすればよい。List 4.4.1 にプログラム例を示す。

List 4.4.1 PBMファイル,PGMファイルを読み込んで,輪郭抽出処理してファイル出力

#include <stdio.h>
#include <string.h> //strcpy()のために必要
#include <math.h> //sqrt()のために必要

#define SCREENSIZE 256

typedef unsigned char byte_t;
typedef struct {
    int magic;    //マジックナンバーの数値の部分
    int width;    //画像の横幅
    int height;   //画像の高さ
    int maxvalue; //画素値の最大値
} pnmheader_t;

/* 必要な関数はここにコピー&ペーストする */

/*****
 * byte型2次元配列の画素値から輪郭抽出する関数extractContour()
 * pnmheader_t pnmheader PNMヘッダ構造体
 *   これから,画素がPBMのものかPGMのものか区別がつく
 *   また有効画素の範囲もわかる
 * byte_t pixelareaIP[][] 画素データ2次元配列(入力,参照元)
 * byte_t pixelareaOP[][] 画素データ2次元配列(出力,参照元を変換して作成)
 * 戻り値 作業に成功したら0,失敗したら-1
 *         作業に失敗とは,pnmheader内のマジックナンバーが1,2以外
*****/
int extractContour(pnmheader_t pnmheader,byte_t pixelareaIP[][SCREENSIZE],byte_t pixelareaOP[][SCREENSIZE])
{
    int ix,iy,tmpx,tmpy,tmp,tmp2;
    if (pnmheader.magic==1) {
        for (iy=1; iy<pnmheader.height; iy++) {
            for (ix=1; ix<pnmheader.width; ix++) {
                tmpx = pixelareaIP[iy][ix]-pixelareaIP[iy][ix-1];
                tmpy = pixelareaIP[iy][ix]-pixelareaIP[iy-1][ix];
                tmp=tmpx*tmpx+tmpy*tmpy;
                if (tmp<=0) pixelareaOP[iy][ix]=0;
                else pixelareaOP[iy][ix]=1;
            }
        }
    } else if (pnmheader.magic==2) {
        for (iy=1; iy<pnmheader.height; iy++) {
            for (ix=1; ix<pnmheader.width; ix++) {
                tmpx = pixelareaIP[iy][ix]-pixelareaIP[iy][ix-1];
                tmpy = pixelareaIP[iy][ix]-pixelareaIP[iy-1][ix];
                tmp2=tmpx*tmpx+tmpy*tmpy;
                tmp=sqrt(tmp2);
                if (255<=tmp) pixelareaOP[iy][ix]=0;
                else pixelareaOP[iy][ix] = 255-tmp;
            }
        }
    } else {  //作業不能
        return -1;
    }
    return 0;
}

byte_t pixelareaIP[SCREENSIZE][SCREENSIZE];
byte_t pixelareaOP[SCREENSIZE][SCREENSIZE];
pnmheader_t header;

typedef struct {
    char *fnamein;
    char *fnameout;
} testfname_t;

testfname_t names[] ={
    {"turtle.pbm","turtlecon.pbm"}, //PBMファイルとして正常に読み込めるはず
    {"turtle.pgm","turtlecon.pgm"}, //PGMファイルとして正常に読み込めるはず
    {"turtle.ppm","turtlecon.ppm"}, //PPMファイルなのでマジックナンバーP3 失敗するはず
    {"abcdef.xyz","abcdefcon.xyz"}, //存在しないファイルなので失敗するはず
//    |            |
//    |            fnameout(出力用ファイル名)
//    fnamein(入力用ファイル名)
};

int main()
{
    char fnameIP[128];//入力用ファイル名
    char fnameOP[128];//出力用ファイル名
    int nTests=sizeof(names)/sizeof(testfname_t);
    int result;
    int test;
    for (test=0; test<nTests; test++) {
        strcpy(fnameIP,names[test].fnamein);// names[test].fnameinをfnameIPにコピー
        strcpy(fnameOP,names[test].fnameout);// names[test].fnameoutをfnameOPにコピー
        header=readPBMPGMFile(fnameIP,pixelareaIP);
        if (header.magic==-1) {
            printf("can't open file [%s]\n",fnameIP);
            continue;
        }
        result=extractContour(header,pixelareaIP,pixelareaOP);
        if (result==-1) {
            printf("[%s] failed extractContour\n",fnameIP);
            continue; //ループの1回分はここで終了したとみなしなさいという命令
        }
        //showPBMPGMImage(header,pixelareaOP);
        result=generatePBMPGMFile(fnameOP,header,pixelareaOP);
        if (result==-1) {
            printf("[%s] failed generate file\n",fnameOP);
            continue;
        }
        printf("[%s][%s] completed\n",fnameIP,fnameOP);
    }
    return 0;
}

図4.4.1 turtle.pbm オリジナル
図4.4.2 turtlecon.pbm 輪郭抽出
図4.4.3 turtle.pgm オリジナル図4.4.4 turtlecon.pgm 輪郭抽出