※自分用メモです
どんどん無駄にカテゴリが増えていくぶっそれんれです。
一番大好きな言語、C++の衰えを肌で感じずにはいられない今日のこのごろ、今日はSoftware Engineering で使うかも知れない画像ジェネレートについてちょっと詳しくやってみようと思います。
コンピュータグラフィックの種類
1ピクセルごとに処理する方式(ペイントツールはこっち)と、ベクトルごとに処理する方式(3Dゲームはこっち)、の二つがあります。ベクトル方式のグラフィックライブラリで有名なのは、言わずと知れた、OpenGLやDirectXのような、3D処理ライブラリですね。
私の得意分野も後者ですが、今回はあえて前者を使います。
方式
今回生成する画像イメージは、BMP形式です。画像形式と言えばJPGやPNG等が最近の主流ですが、BMPは1ドット1ドットを直接RGB(24ビット)で表せるのがメリットです。JPGやPNGは、圧縮をかけているためこのルールが当てはまりません。エンコード、デコードの処理が加わってしまうので、今回はそんな事をしないで済むようにBMP形式を選択しました。JPGやPNGへの圧縮は何かしらのライブラリを用いて圧縮をかけてください。
ただ先に言おう。windowsデフォのデータ形式は、いちいち処理がめんどくさい\(^o^)/www
BMPの構造
BMPは、ヘッダ部、情報部、データ部の3パートで構成されます。ヘッダ、情報部に関しては、windows.hで扱いやすいように構造体が定義されているので、それを使います。※WORDは2バイト長、DWORDは4バイト長のwindows.hで定義された変数です。Linux系で使うときは、DWORDをint WORDをshortにしてから扱うといいと思います。
ヘッダ部:BITMAPFILEHEADER 構造体(14 byte)
address | メンバ | 型(byte) | 意味 |
---|---|---|---|
0x0000 | bfType | WORD(2) | 2バイトで"BM"という二文字が格納されている |
0x0002 | bfSize | DWORD(4) | 画像データのファイルサイズ |
0x0006 | bfReserved1 | WORD(2) | 予約領域1 |
0x0008 | bfReserved2 | WORD(2) | 予約領域2 |
0x000a | bfOffBits | DWORD(4) | 情報部修了までのオフセット(58固定) |
特にいうことはありません。予約領域はそのまま0,0で埋めてください。
情報部: BITMAPINFOHEADER 構造体(36 byte) LONGの定義によって異なるが今回は(無理やり)LONG=4バイト固定で行く
address | メンバ | 型(byte) | 意味 |
---|---|---|---|
0x000e | biSize | DWORD(4) | 情報部のサイズ。sizeofで定義すれば問題ない |
0x0012 | biWidth | LONG(4) | 画像の横幅 |
0x0016 | biHeight | LONG(4) | 画像の建て幅 |
0x001a | biPlanes | WORD(2) | プレーン?1固定。きっとBitMapが将来レイヤー構造に対応するのか |
0x001c | biBitCount | WORD(2) | 1ピクセル辺りのビット数。フルカラーなので24固定 |
0x001e | biCompression | DWORD(4) | 圧縮形式。今回は向圧縮なので0 |
0x0022 | biSizeImage | DWORD(4) | イメージのサイズ |
0x0026 | biXPelsPerMeter | LONG(4) | 解像度。よくわからないけど1固定 |
0x002a | biYPelsPerMeter | LONG(4) | 解像度。よくわからないけど1固定 |
0x002e | biClrUsed | DWORD(4) | パレットを使用しないので0 |
0x0032 | biClrImportant | DWORD(4) | 意味不明。0固定 |
※LONGは一応今の仕様では4バイトですが、今後8バイトに変わるかもしれません。この辺はっきりしてほしい感じはありますねぇ……Cェ……
いまどきあまり2色、16色、256色とかあまり使わない上、今日は綺麗な画像を生成してこいとかいう、ちょっとした無茶ぶりを強いられている24ビットフルカラーのみ解説。
BMP形式の画像データは左から右、下から上。という風に、数学的なxy座標をとっています。(通常、コンピュータのxyの原点は左上。)なので、数学になれている人が扱う分には良いのですが、コンピュータグラフィックになれてる人が扱うと、上下逆向きに処理してしまう恐れがあります。
さらに曲者な仕組みがあります。1行(?)ごとにパディングするシステムです。1行の画像データが4の倍数バイトで納まらなかったら、0というデータを付随します。わけがわからないよ(←) 要するに、50×50の画像を生成すると、1行50×3バイト(24ビットカラー)×50行=7500バイトのデータで済みそうなのですが、1行150バイトは4の倍数ではありません。0x0000を付随して、1行152バイトにします。からの×50行。データ量が100バイトも増えてもったいないじゃないかっっ。
24ビットカラーの場合、RGBの順番で並んでいます。パディングにさえ気をつけて左下から順番通りに1ピクセルづつデータを並べていけば問題なさそうですね。
以上で、データ構造の解説は大体終わります。
これを踏まえてサンプルプログラムを作ります。
プログラムの方針
今回使用する言語はC++です。ちょっとクラスチックに、楽ーに楽ーに。
普段使っている開発環境はVisual Studio 2008 or 2010 ですが、gccも時々使うんで両方対応できるようにwindows.hはインクルードしません。全部ヘッダで定義します。1ドット1ドットずつ処理したいので、それ以外のややこしい部分はクラスでまかないます。
というわけで、これを踏まえてサンプルコードをちょっと作ってみました。
一応公開してますが、再利用とか考えないでくださいwww かなり適当で酷いソースコードです\(^o^)/
ソースコードに関して。特に特筆することはありません。めんどくさかったので、Managerクラスの中で描画テストやってますが、本来ならfriendクラスなりメソッドなりなんなりを用いて、外部から自由に描画出来る機能を用意すべきですが、まあそれは次の段階で。
というわけで、1ドットずつ、y=cos(x)の処理をかけてみました。1ドットずつ処理しているので、点線のような感じになってしまいます。
本来なら、線の太さと、それに対するどっと間の補完的な処理が必要なのですが、今回はとりあえずbmpをいじれるようにする下地を作るのがメインだったので、今回はこれにておしまい\(^o^)/
ソースコードは続きを読むをクリックしてくださいまし。
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <math.h> // 画像ジェネレートテスト // windows . UNIX 両方対応できる工夫をここで行う。 typedef unsigned char BYTE; /* 1byte符号なし整数 */ typedef unsigned short WORD; /* 2byte符号なし整数 */ typedef unsigned long DWORD; /* 4byte符号なし整数 */ typedef long LONG; /* 4byte整数 */ #pragma pack(1) typedef struct BMPFILEHEADER{ char bfType[2]; DWORD bfSize; WORD bfReserved1; WORD bfReserved2; DWORD bfOffBits; }BMPFILEHEADER; typedef struct BMPINFOHEADER{ DWORD biSize; LONG biWidth; LONG biHeight; WORD biPlanes; WORD biBitCount; DWORD biCompression; DWORD biSizeImage; LONG biXPelsPerMeter; LONG biYPelsPerMeter; DWORD biClrUsed; DWORD biClrImportant; }BMPINFOHEADER; typedef struct RGBCOLOR{ BYTE rgbBlue; BYTE rgbGreen; BYTE rgbRed; }RGBCOLOR; #pragma pack() // 管理用のクラス class bmpManager { private: BMPFILEHEADER bmpHeader; BMPINFOHEADER bmpInfo; protected: unsigned int nWidth; // 横幅 unsigned int nHeight; // 縦幅 RGBCOLOR *rgbImage; // 画像データの本体 public: // コントラクタ bmpManager() { memset(&bmpHeader,0,sizeof(BMPFILEHEADER)); memset(&bmpInfo,0,sizeof(BMPINFOHEADER)); }; // デストラクタ ~bmpManager() { free(rgbImage); }; // 画像データの生成 int CreateBMPImage(unsigned int width,unsigned int height); // 画像データの吐き出し int OutputBMPImage(char *filename); // ヘッダの作成 void MakeHeader(); // 画像データをいじりまわす!!(ぁ void Generate(); }; /**-------------------------------------------------------------------------------- ビットマップイメージをメモリ上に作成する。作成されたBMPはとりあえず、真っ黒で生成される。 @param width 横幅 @param height 建て幅 @return エラーコードを返す。基本的に1なら成功 --------------------------------------------------------------------------------*/ int bmpManager::CreateBMPImage(unsigned int width,unsigned int height) { this->nHeight = height; this->nWidth = width; rgbImage = (RGBCOLOR*)malloc(sizeof(RGBCOLOR)*width*height); memset(rgbImage,0,sizeof(RGBCOLOR)*width*height); return 1; } /**-------------------------------------------------------------------------------- ファイルにビットマップイメージを出力する @param filename 出力先のファイル名 @return エラーコードを返す。基本的に1なら成功 --------------------------------------------------------------------------------*/ int bmpManager::OutputBMPImage(char *filename) { FILE *fp; fp = fopen(filename,"wb"); if(fp==NULL){ perror("Can't open the file."); return 0; } // ヘッダ部分を生成、書き込み MakeHeader(); fwrite(&bmpHeader,sizeof(BMPFILEHEADER),1,fp); fwrite(&bmpInfo,sizeof(BMPINFOHEADER),1,fp); // データ部分を一行ずつ書き込み unsigned int i; char buf[4]=""; for(i=0;i<nHeight;i++){ fwrite(&rgbImage[i*nWidth],sizeof(RGBCOLOR),nWidth,fp); fwrite(buf,sizeof(char),nWidth%4,fp); } fclose(fp); return 1; } /**-------------------------------------------------------------------------------- ファイルヘッダ、情報部ヘッダを作成する --------------------------------------------------------------------------------*/ void bmpManager::MakeHeader() { int linedsize; // 1行のバイトサイズの計算がめんどくさい!w linedsize = (nWidth+3)/4; linedsize *=4; // file header bmpHeader.bfReserved1=0; bmpHeader.bfReserved2=0; bmpHeader.bfType[0]='B'; bmpHeader.bfType[1]='M'; bmpHeader.bfOffBits = sizeof(BMPFILEHEADER)+sizeof(BMPINFOHEADER); bmpHeader.bfSize = linedsize * nHeight * 3+bmpHeader.bfOffBits; // info header bmpInfo.biSize = sizeof(BMPINFOHEADER); bmpInfo.biWidth = nWidth; bmpInfo.biHeight = nHeight; bmpInfo.biPlanes = 1; bmpInfo.biBitCount = 24; bmpInfo.biCompression = 0; bmpInfo.biSizeImage = linedsize * nHeight *3; // 1行あたりのバイト数×高さ×24bit bmpInfo.biXPelsPerMeter = 1; bmpInfo.biYPelsPerMeter = 1; bmpInfo.biClrUsed = 0; bmpInfo.biClrImportant = 0; return; } int main() { int w,h; bmpManager bmp; printf("横幅、縦幅を入力してください\n"); scanf("%d",&w); scanf("%d",&h); bmp.CreateBMPImage(w,h); bmp.Generate(); if(!bmp.OutputBMPImage("test.bmp")) printf("エラーエラー"); return 0; } #define PI 3.1415926535897932384626433832795028841971 void bmpManager::Generate() { // y = cos(x)で作ってみる int x,y; double t; DWORD i; for(i=0;i<nWidth*100;i++){ t = i/100; y = (int)(nHeight/4*cos(PI/64*t))+nHeight/2; x = (int)(t+0.5); rgbImage[y*nWidth+x].rgbRed=255; rgbImage[y*nWidth+x].rgbBlue=255; rgbImage[y*nWidth+x].rgbGreen=255; } return; }