プログラミング演習Ⅱ 課題 4 第 2 週 画像ファイル (ppm) の読み書き 画像データ用のメモリ確保・解放 1
B1B2B3 B4B5B6 B7B8B9 G1G2G3 G4G5G6 G7G8G9 画像データの基礎 2 画素ごとの明るさを数値で表現 (8bit なら 0 から 255) カラー画像は各画素に Red, Green, Blue (R,G,B)= (255, 0, 0): 赤, (255, 255, 255): 白 raw 画像ファイルでは 1 次元上に数値が並んでいる R1G1B1R2G2B2...B3R4G4... ファイル上の画像データの並び R1R2R3 R4R5R6 R7R8R9
PPM 画像ファイル 3 画像に関する情報をヘッダとして持っている 今回使用する画像データは R,G,B 各 8 ビット P6 # Created by IrfanView [ ここから画像データ ]..... PPM ファイルの中身 マジックナンバー ( バイナリ PPM, 固定 ) # から改行まではコメント文 横方向の画素数 [ 空白 ] 縦方向の画素数 最大輝度 [ ここから画像データ ]..... 解説 R1G1B1R2G2B2...B3R4G4... 8bit 24bit
4 データ構造 ~画像データをどう表現するか~ 画像ファイルのフォーマット ヘッダ ( 画像サイズ,最大輝度 ) 画像データ 88 (136) B1 (177) E7 (231) int xsize; int ysize; int level; unsigned char r1, g1, b1, r2... プログラムでデータを読み込む場合・・・ ・画像 1 つに対し変数がいくつも必要 → 構造体でまとめ る ・画像データの配列サイズはヘッダを読むまで決められ ない → メモリの動的確保・解放
5 データ構造 ~構造体定義~ 画像ファイルのフォーマット ヘッダ ( 画像サイズ,最大輝度 ) 画像データ 88 (136) B1 (177) E7 (231) typedef struct { int xsize; int ysize; int level; PIXEL **pBuffer; } IMAGE; ヘッダ 画像データへの二重ポイン タ typedef struct { unsigned char r; unsigned char g; unsigned char b; } PIXEL; 画像データ (1 画素分 )
6 データ構造 ~構造体定義~ 画像ファイルのフォーマット ヘッダ ( 画像サイズ,最大輝度 ) 画像データ 88 (136) B1 (177) E7 (231) typedef struct { int xsize; int ysize; int level; PPIXEL *pBuffer; } IMAGE; typedef struct { unsigned char r; unsigned char g; unsigned char b; } PIXEL, *PPIXEL; (PIXEL*) 型として PPIXEL を新たに定義
7 ポインタの配列 ~画像バッファの確 保~ void iioMallocImageBuffer(IMAGE *pImage) xsize ysize level pBuffer xsize 個の PIXEL の配列を確保 malloc(xsize * sizeof(PIXEL)); pImage ysize 個の PPIXEL の配列を確保 malloc(ysize * sizeof(PPIXEL));
8 画像バッファの様子 ysize 個 xsize 個 xsize ysize level pBuffer pImage → i→ i ↓j↓j pImage->pBuffer[0][0] ↓j↓j → i→ i pImage->pBuffer[j][i] pImage->pBuffer[j]
画像へのアクセス方法 9 IMAGE *pImage; /* pImage に画像データを読み込んだ後と仮定する */ pImage->xsize; /* int 型,画像幅 */ pImage->ysize; /* int 型,画像高さ */ pImage->pBuffer[j][i]; /* PIXEL 型,位置 (i,j) における {R,G,B}*/ pImage->pBuffer[j][i].r; /* unsigned char 型 */ /* 位置 (i,j) における R の値 */ pImage->pBuffer[j]; /* PPIXEL 型, (j) 行の画素値が連続で格納されて /* いる場所の先頭アドレス */
モジュール設計について 10
11 モジュール設計 利点 : 可読性,分割コンパイル,情報の隠 ぺい モジュールごとの独立性を高めるように設 計 メイン main.c 画像処理 img_proc.c img_proc.h 画像のファイル入出力 画像データ用メモリ確保と解放 img_io.c img_io.h
12 関数プロトタイプ宣言 double func(double d); int caller(void) { f = func(4); } double func(double d) { return d*2; } 関数呼出部分より前に プロトタイプ宣 言 を行う 最後はセミコロ ン あるファイル内で定義した関数 (func) を,定義し ている位置より前で呼び出したい場合 プロトタイブ宣言がないと, func 関数は未定義エラー func 関数の定義部
13 ヘッダファイルの利用 #include “module.h” // 呼び出される関数 double func(double d) { return d*2; } module.c #include “module.h” // 呼び出し側関数 int caller(void) { f = func(4); // 関数呼び出 し } caller.c // プロトタイプ宣言 double func(double d); module.h ヘッダファイル ” ” で囲んでいる ことに注意! あるファイル内で定義した関数 (func) を,違う ファイルで呼び出したい場合 関数のプロトタイプ宣言をヘッダファイルに記述 呼び出し側のファイルで #include
14 と " " #include のように で囲まれて いると,プリプロセッサはシステムで定められた 場所にあるファイルを取り込む #include “module.h” のように ” ” で囲まれて いると,まずプログラムファイルと同じ場所を探 し,存在しない場合はシステムで定められた場所 を探す
15 モジュール化の基本 #include #include “module.h” int internal_func(int a); #define INT_MACRO(a) (…) static int internal_func(int a) { // モジュール内で // 使用する関数 } module.c module.h void exported_func(int i) { // 外部に公開する関数 } ヘッダファイルには 外部に公開する情報のみを記述 関数のプロトタイプ宣 言 typedef 型宣言 #define マクロ定義 など プロトタイプ宣言が関数本体と一 致するように注意 モジュール内のみで使用する関数 のプロトタイプ宣言やマクロ定義 などはモジュール内で行う モジュールで,何を公開し, 何を隠すかがポイント #define MAX 1024 typedef unsigned char BYTE; void exported_func(int i);
16 ファイルを分割したときの関数のス コープ × ○ #include #include “module.h” int internal_func(int a); #define INT_MACRO(a) (…) static int internal_func(int a) { // モジュール内で // 使用する関数 } module.c module.h void exported_func(int i) { // 外部に公開する関数 } #define MAX 1024 typedef unsigned char BYTE; void exported_func(int i); 別のモジュール #include “module.h” exported_func(15); internal_func(30); foo.c ヘッダファイルにプロトタイプ宣言がない 関数は外部から参照できない static な関数は外部から参照できない
サンプルプログラム 2 につい て 17
18 全体の流れ 画像ファイルを開き,画像データをメモリ上にロード ロードした画像データに処理を加える 処理後のデータを出力ファイルに書き出す 画像データ用に確保したメモリを解放
19 img_io.c 画像ファイルのロード ファイルオープン 画像バッファ確保 データコピー ファイルクローズ 画像ファイルのセーブ 画像バッファ確保 画像バッファ解放 サンプルプログラム 2 の関数呼び出し 関係 main.c 画像ファイルをロードする関数を呼び出す 画像処理関数を呼び出す 画像ファイルをセーブする関数を呼び出す 画像バッファを解放 img_proc.c 画像を 90 度回転 作業用バッファを確保 データを移し変え 不要なバッファを解放 ( あとで処理を追加したい ) etc
20 img_io.c 画像ファイルのロード ファイルオープン 画像バッファ確保 データコピー ファイルクローズ 画像ファイルのセーブ 画像バッファ確保 画像バッファ解放 サンプルプログラム 2 の実装状況 main.c 画像ファイルをロードする関数を呼び出す 画像処理関数を呼び出す 画像ファイルをセーブする関数を呼び出す 画像バッファを解放 img_proc.c 画像を 90 度回転 作業用バッファを確保 データを移し変え 不要なバッファを解放 ( あとで処理を追加したい ) etc 実装済 演習課題 実装済 演習課題 ( 第 3 週以降 )
21 関数の設計 ファイル入出力 (済) int iioLoadFile(IMAGE *pImage, const char *fname); (未) int iioSaveFile(IMAGE *pImage, const char *fname); メモリ確保 / 解放 (済) void iioMallocImageBuffer(IMAGE *pImage); (未) void iioFreeImageBuffer(IMAGE *pImage); 画像処理関連 (済) void ipRotateImage(IMAGE *pImage);
第 2 週目の目標 画像ファイルを読み込み 90° 回転させた後, 画像ファイルを出力するプログラムを作成す る 読み込み関数や回転処理関数は既に実装済み 画像ファイルをセーブする関数 ”iioSaveFile” と 画像バッファを解放する関数 ” iioFreeImageBuffer” を完成させる 22
23 画像ファイルのセーブのヒン ト 出力用ファイルを バイナリモードでオープン ヘッダ情報を出力 画像データを出力 ファイルクローズ 開始 終了 int iioSaveFile(IMAGE *pImage, const char *fname) xsize ysize level pBuffer pImage pBuffer[0] pBuffer[1] pBuffer[2] 一行ずつ出力 ヒント: fwrite ヒント 1: fprintf ヒント 2: xsize の後ろに空白 1 つ ysize の後ろに改行 level の後ろに改行 を忘れない
24 画像バッファの解放のヒント void iioFreeImageBuffer(IMAGE *pImage) pBuffer を解放 ② 開始 終了 j < ysize? xsize ysize level pBuffer pImage pBuffer[0] pBuffer[1] pBuffer[2] ① 一行ずつ解放 ②最後に各行へのポイ ンタの配列を解放 pBuffer[ j ] を解放 ① j++; j =0; Y N