1.画像基礎
ここでは画像フォーマットが簡単な ppm (portable pixmap) を利用します.ppm は画素ごとに RGB それぞれに 8 bit (合計 3×8 = 24 ビット)のカラー画像が表現できます.データ形式はバイナリ形式(raw)とアスキー形式(ascii)がありますが,まずはバイナリ形式を利用します. 画像フォーマットは,
P6
XSIZE YSIZE
最大階調
画像データ:バイナリ形式で左上から右下へRGBRGBRGB…とデータが並ぶ
のようになっています.また一行目以外の場所に # でコメントを入れることも可能です.例えば,640×480 の画像を作りたければ,
P6
640 480
255
RGBRGBRGB...(実際はバイナリ形式)
のように記述します.次のプログラムは 640×480 の真っ白の画像(white.ppm)を作るためのものです.
#include <stdio.h> main(){ unsigned char image[480][640][3]; int x,y; FILE *fp; fp=fopen("white.ppm","wb"); fprintf(fp,"P6\n#←シャープでコメントも入れられる\n640 480\n255\n"); //ヘッダの書き込み for(y=0;y<480;y++){ for(x=0;x<640;x++){ image[y][x][0]=image[y][x][1]=image[y][x][2]=0xff; } } fwrite(image,sizeof(char),640*480*3,fp); //データの書き込み fclose(fp); } |
画素値は大きいほど色が濃く,最大値は 0xff ( = 255) です.色の三原色ではすべてを混ぜると黒になるますが,光の三原色では全てを混ぜると白になるので注意して下さい.つまり RGB の全てが 0xff になると白になり,全てが 0 で黒になります.
|
|
こんなに単純で便利なフォーマットであるにも関わらず,Windows の場合は標準では開くことができません.IrfanView や Susie などをインストールして ppm の画像を見ることのできる環境を作ってください.linux なら xv や gimp で開くことができます.
まず,sample.ppm をダウンロードして,自分の使っているマシン環境で ppm ファイルを見ることができるかを確認しましょう.確認できれば,下の演習に取り掛かってください.
演習1−1
|
|
|
|
これまでの例ではバイナリ形式で画素値を書き込みました.次にアスキー形式で書き込む ppm について説明します.
アスキー形式の ppm ファイルはヘッダ部分が P3 になります.画素値は 10 進数ですので,%d で書き込みます.テキストなので,fopen をするときは "w" になるので注意して下さい.P3 で真っ白の画像を作るプログラムは次のようになります.
#include <stdio.h> main(){ unsigned char image[480][640][3]; int x,y; FILE *fp; fp=fopen("whiteP3.ppm","w"); fprintf(fp,"P3\n#←シャープでコメントも入れられる\n640 480\n255\n"); //ヘッダの書き込み for(y=0;y<480;y++){ for(x=0;x<640;x++){ image[y][x][0]=image[y][x][1]=image[y][x][2]=0xff; fprintf(fp, "%d %d %d\n", image[y][x][0], image[y][x][1], image[y][x][2]); //データの書き込み } } fclose(fp); } |
画素値の区切りはスペース,タブ,改行のいずれかを利用します.
演習1−2
それでは今回作った white.ppm と whiteP3.ppm のファイルのサイズを見比べてみてください.white.ppm が 901KB に対して,whiteP3.ppm は 3901KB です.テキストであるがためにファイルサイズが大きくなってしまいます.それにテキストで値が見られるとはいっても,(300,20) の値は?といわれてもテキストのどこになるのか探すのは難しいです.それにテキストの方がファイルを書き込むのにも時間がかかります.
という訳で,これからはバイナリ形式の P6 を使っていきます.
これまでのプログラムでは,直感的に理解しやすいように画像を収納するメモリを,unsigned char image[480][640][3]; の3次元配列で宣言しました.しかし,もちろん unsigned char image[480*640*3]; のように一次元配列でも記述することができますし,unsigned char *image; のようにポインタを使って記述することもできます.
image[480][640][3] で宣言されている場合には,image[y][x][i] と *(image+3*(640*y+x)+i) が同じ意味であることを理解していれば,プログラムの本質は変わりません.両者が同じ意味になることが分からない人はもう少しポインタを勉強しましょう.
次のプログラムは,1−1で作った gradation.ppm をファイルに取り込んで左右反転した画像 reverse.ppm を作るプログラムです.
#include <stdio.h> main(){ unsigned char image[480][640][3]; unsigned char ctmp; int x,y,i; FILE *fp; if((fp=fopen("gradation.ppm","rb"))==NULL){ printf("ファイルが開けません\n"); exit(-1); } fscanf(fp,"P6\n#←シャープでコメントも入れられる\n640 480\n255\n); //ヘッダを読み飛ばす fread(image,sizeof(char),640*480*3,fp); fclose(fp); for(y=0;y<480;y++){ //データの左右を反転する for(x=0;x<640/2;x++){ for(i=0;i<3;i++){ ctmp = image[y][x][i]; image[y][x][i] = image[y][639-x][i]; image[y][639-x][i] = ctmp; } } } fp=fopen("reverse.ppm","wb"); fprintf(fp,"P6\n#←シャープでコメントも入れられる\n640 480\n255\n"); fwrite(image,sizeof(char),640*480*3,fp); fclose(fp); } |
演習1−3
|
|
|
以前のコンパイラでは大きな配列はうまく確保できなかったので,ポインタで malloc をしないとプログラムが動かなかったりしたのですが,最近のコンパイラは大きな配列もうまく確保してくれるようです.それでもやはり大きな配列を使う時は,ポインタを使用した方が無難です.