54 章
今回は、画像から弾幕データに変換してみようという章です。
線で書かれた絵の線上に弾を置いていって、弾幕データを作ります。
この章で使う線画はこれです。「AA画像」というフォルダを作ってそこに入れておきました。
緑青黒羽さんに書いていただきました。ありがとうございます。(緑青黒羽さんのサイトはこちら)
今回もまた、新しくプロジェクトを作ります。作るのが面倒な人は、同梱されているプロジェクトを、そうでない人は新しく作って下さい。
では、今回は画像を扱うということで、画像についての知識が必要です。
ビットマップファイル(.bmp)というやたら重い画像ファイル形式がありますよね。
あれは無圧縮画像ファイルといって、データが生データとして入っている画像です。
そのためデータは重いですが、非常に取り扱いが簡単です。
DXライブラリでは色を光の三原色のそれぞれ輝度0〜255で表していましたね。
ビットマップも同じです。一つのピクセルは赤・青・緑の三色の輝度それぞれ0〜255で表されています。
ということは、データを読み込んで
typedef struct{
unsigned char col[3];
}img_t;
こんな感じの構造体に色データを入れてやれば良さそうです。
ただ、ファイルには「ヘッダ」と呼ばれるものが存在します。
「この画像は○X○サイズですよ〜。容量はどれ位ですよ〜。規格は何ですよ〜。」
そういう情報がデータの先頭についています。このヘッダ部が54バイトあって、本体のデータがあるんです。
ですから、読み込むときは、54バイト飛ばしてから読み込めばOKです。
ビットマップについては、この辺のサイトに詳しく書かれています。
また、弾をそこに置くかどうかを決めるのに、0〜255の輝度は必要ありません。
むしろそこに「色があるか無いか」の2種類に分ける必要があります。
こういうのを「2値化」といいます。2値化する為には、輝度の平均が128以上か、以下かでわけてやればよいでしょう。
とりあえずこの章では
1. 読み込んで
2. 色を格納する構造体に色情報を入れて
3. 2値化して
4. 描画
それだけやってみましょう。
/* ビットマップ画像の横サイズ */
#define BMP_YOKO 400
/* ビットマップ画像の縦サイズ */
#define BMP_TATE 400
/* ビットマップのヘッダサイズ */
#define HEAD 54
/* トータルサイズ */
#define TOTAL (BMP_YOKO*BMP_TATE*3+HEAD)
/* AAデータを作成する合計枚数 */
#define BULLET_MAX 4000
/* ビットマップの色情報を格納するための構造体 */
typedef struct{
unsigned char col[3];
}img_t;
/* TOTAL分データを用意 */
unsigned char data[TOTAL];
/* イメージ画像のピクセル数だけ色格納用構造体を用意 */
img_t img[BMP_TATE][BMP_YOKO];
/* 2値化した情報を入れるための配列を用意 */
BYTE Pixel[BMP_TATE][BMP_YOKO];
とりあえずこれだけ変数と定義を用意しておきましょう。
画像は400x400にしておきます。
TOTALはヘッダを合わせたデータのバイトサイズです。
BULLET_MAXは今は使いません。
/* ビットマップを読み込みdataに格納する関数 */
int ReadBmp(){
char name[256]="../AA画像/aisha.bmp";
FILE *fp;
fp = fopen( name , "rb" );
if( fp == NULL ){
printfDx( "%sが見つかりません。",name);
return -1;
}
fread( data, TOTAL, 1, fp );
fclose(fp);
return 0;
}
ビットマップ画像を読み込みます。これは簡単ですね。
/* ビットマップの生データを各ピクセルの色格納用構造体に入れ直す */
void ConvData(){
int x,y,c,t;
t=HEAD;
for(y=BMP_TATE-1;y>=0;y--){
for(x=0;x<BMP_YOKO;x++){
for(c=0;c<3;c++){
img[y][x].col[c]=data[t];
t++;
}
}
}
}
データは
「青緑赤青緑赤青緑赤・・・・」
と連続してdataに入っていますから、解り易いようにimg構造体に入れなおします。
/* 2値化 */
void Binarization(){
int x,y,c;
int sum;
for(y=0;y<BMP_TATE;y++){
for(x=0;x<BMP_YOKO;x++){
sum = 0;
//色の輝度平均を計算
for(c=0;c<3;c++){
sum += img[y][x].col[c];
}
sum /= 3;
//0〜255の輝度の平均が128以上なら(明るいなら)0、未満なら(暗いなら)1
if( sum >= 128){
Pixel[y][x] = 0;
} else {
Pixel[y][x] = 1;
}
}
}
}
2値化します。sumで3色の輝度の平均を計算し、それが128以上か未満かで判断します。
2値化し、そこに色が無い(明るい)なら0、ある(暗い)なら1をPixelに格納します。
後はPixelの内容を描画してるだけです。
ではプログラム全体を見てみましょう。
マウス、キーボードについてのファイルは前章と同様です。
#include "../../../include/DxLib.h"
#include "Key_Mouse.h"
/* ビットマップ画像の横サイズ */
#define BMP_YOKO 400
/* ビットマップ画像の縦サイズ */
#define BMP_TATE 400
/* ビットマップのヘッダサイズ */
#define HEAD 54
/* トータルサイズ */
#define TOTAL (BMP_YOKO*BMP_TATE*3+HEAD)
/* AAデータを作成する合計枚数 */
#define BULLET_MAX 4000
/* ビットマップの色情報を格納するための構造体 */
typedef struct{
unsigned char col[3];
}img_t;
int Key[256];
/* TOTAL分データを用意 */
unsigned char data[TOTAL];
/* イメージ画像のピクセル数だけ色格納用構造体を用意 */
img_t img[BMP_TATE][BMP_YOKO];
/* 2値化した情報を入れるための配列を用意 */
BYTE Pixel[BMP_TATE][BMP_YOKO];
/* ビットマップを読み込みdataに格納する関数 */
int ReadBmp(){
char name[256]="../AA画像/aisha.bmp";
FILE *fp;
fp = fopen( name , "rb" );
if( fp == NULL ){
printfDx( "%sが見つかりません。",name);
return -1;
}
fread( data, TOTAL, 1, fp );
fclose(fp);
return 0;
}
/* ビットマップの生データを各ピクセルの色格納用構造体に入れ直す */
void ConvData(){
int x,y,c,t;
t=HEAD;
for(y=BMP_TATE-1;y>=0;y--){
for(x=0;x<BMP_YOKO;x++){
for(c=0;c<3;c++){
img[y][x].col[c]=data[t];
t++;
}
}
}
}
/* 2値化 */
void Binarization(){
int x,y,c;
int sum;
for(y=0;y<BMP_TATE;y++){
for(x=0;x<BMP_YOKO;x++){
sum = 0;
//色の輝度平均を計算
for(c=0;c<3;c++){
sum += img[y][x].col[c];
}
sum /= 3;
//0〜255の輝度の平均が128以上なら(明るいなら)0、未満なら(暗いなら)1
if( sum >= 128){
Pixel[y][x] = 0;
} else {
Pixel[y][x] = 1;
}
}
}
}
/* 描画 */
void Graph(){
int x,y;
static int Black = GetColor(0,0,0);
DrawBox(0,0,BMP_YOKO,BMP_TATE,GetColor(255,255,255),TRUE);
for(y=0;y<BMP_TATE;y++){
for(x=0;x<BMP_YOKO;x++){
if( Pixel[y][x] == 1){
DrawPixel(x,y,Black);
}
}
}
}
int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow ){
SetGraphMode(BMP_YOKO,BMP_TATE,16);//400x400に
SetWindowSizeChangeEnableFlag(TRUE);//画面の大きさを変更できる
ChangeWindowMode(TRUE);//ウィンドウモード
if(DxLib_Init() == -1 || SetDrawScreen( DX_SCREEN_BACK )!=0) return -1;//初期化と裏画面化
ReadBmp();//ビットマップ読み込み
ConvData();//画像の色情報構造体に格納
Binarization();//2値化
while(ProcessMessage()==0 && ClearDrawScreen()==0 && GetHitKeyStateAll_2(Key)==0 && Key[KEY_INPUT_ESCAPE]==0){
//↑メッセージ処理 ↑画面をクリア ↑入力状態を保存 ↑ESCが押されていない
Graph();//描画
ScreenFlip();
}
DxLib_End();
return 0;
}
実行結果
2値化したPixel配列の中身を元にDrawPixelしているので、ビットマップ画像からきちんとデータを読み込み、変換できていることがわかると思います。
- Remical Soft -