第11回 コーディング(2)(6月29日)
■ いよいよ画像の操作
前回(第10回)にて、「選手の基本データ(f1_pilot_info.txt)」と「スコアテーブル(f1_score_table.txt)」をロードする部分を実現した。その結果、ネームカードの配列変数に選手のデータが格納できた。
今回は、選手のデータとして、新たに、画像データを追加すること、および、総合画像を生成することの2点を実現する。
そのためには、前回のプログラムに、画像処理のためのサブルーチンを追加する必要がある。それは、第3回の演習で作成したので、再利用する。
■ 開発用ディレクトリの準備
プログラムの開発段階ごとにディレクトリを設けると便利である。今回は、lect11 というディレクトリを作成し、必要なファイルを集め直す。
前回のプログラムを用意しよう(以下、c/h は、C言語ソースファイルとヘッダファイルのこと)。
- ネームカード:namecard.c/h
- タブ区切テキスト:tabstr.c/h
- 選手データのローダ:load_pilot_info.c/h
- スコアのローダ:load_score_table.c/h
- 基本データ:f1_pilot_info.txt
- スコアテーブル:f1_score_table.txt
第3回の演習で作成したプログラムを再利用しよう。
- 整数の2次元配列:array2dint.c/h
- PGM 画像処理:pgmimage.c/h
画像ファイルは、「S.ベッテル」を加えた PGM/ を使用する。
■ プログラムの作成手順
(1) 画像データのロード
基本データをロードする際、画像ファイル名が得られる(拡張子は除く)。load_pilot_info を実行した後で、画像ファイルをロードするサブルーチンを作成しよう。そのために、以下の拡張をする。
- ネームカードに画像データ領域を作成:namecard.h で NameCard の構造を定義している。ここのメンバに、2次元配列へのポインタを設ける。
- アクセサを作成:namecard.c/h にアクセサとして、nc_put_image(登録) および nc_get_image(参照)を追加する。array2dint.c/h に配列の幅と高さを参照するためのアクセサとして、array2dint_get_width と array2dint_get_height を追加する。
画像ファイルのロードは、load_pilot_images というサブルーチンで実施するようにプログラムを作る。このサブルーチンは、NameCard の配列を先頭から順に処理をするもので、各処理においては、以下を実行する。
- 「パス+ファイル名+拡張子」の文字列を生成
- PGMファイルのオープン
- PGMファイルのヘッダロード
- 2次元配列の確保
- 本体ロード
- PGMファイルのクローズ
- ネームカードへ、2次元配列のポインタを登録
(2) 総合画像の生成
全員の画像を縦に並べることにしよう。そのために、考えておくことは、総合画像のための2次元配列をどの程度確保するのか、そこに画像をどうやって配置するのか、の2点である。このためのサブルーチンを img_cat_all_vertically としよう。
- ネームカードの配列を先頭から順にチェックして、画像の縦の長さの合計値、および、画像の横の長さの最大値、を求める。
- 得られたサイズで2次元配列を確保する。
- 画像の初期値を 0 とする(背景色となる)。
- ネームカードの配列を先頭から順にチェックして、コピーとペーストを適宜実行する(第3回の演習を思い出そう)。
(3) 総合画像のセーブ
img_cat_all_vertically より総合画像を得た後、pgmimage.c/h を用いてファイルに出力する。
補足
上記の実現において、画像ファイルを集めたディレクトリへの「パス」、および、出力する総合画像の「ファイル名」を実行時に与える必要がある。以下の形式でコンソール上からコマンドの入ることを想定しよう。
./prac1101 f1_pilot_info.txt PGM/ f1_score_table.txt result.pgm
第1引数は「基本データ」、第2引数は「画像ファイルデレクトリ名」、第3引数は「スコアテーブル」、第4引数は「出力画像ファイル名」である。
■ コンパイル上の問題
サブルーチンが多くなると、#include の是非、ヘッダファイルの書き方、Makefile の依存関係の定義が混乱しやすい。そこで次の方針をとるとよい。
- ヘッダファイルには、重複するインクルードによる弊害を回避するために、「#ifndef ○○○○」と「#define ○○○○」と「#endif」を導入する。
- ヘッダファイル内では、データ型や定数を使う範囲で、他のヘッダファイルをインクルードする。
- 本体の C のファイル内でも、他のサブルーチンをコールしたり、データ型や定数を使用する範囲で、他のヘッダファイルをインクルードする。
- Makefile での依存関係は、本体の C のファイルとヘッダファイルのいずれかでインクルードしているヘッダファイルを参照する。main 関数を持つソースコードについては、インクルードしているヘッダファイルの他にその本体のオブジェクトファイル(.o)を依存関係に書く。
■ ヒント
動作確認のために、参考となるファイルを示す:
(c) Masato TOKUHISA (2007, June 25th.)