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