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 -