授業用

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