第五十四章 图像→弹幕 试着制作变换工具吧(1)
在本章中,我们来试着把图像变换为弹幕数据吧。
我们通过在有勾线的图像的勾线上放置子弹,来制作弹幕数据。
在这一章中使用的线画是这个。我先新建了一个叫做“AA画像”的文件夹然后把这张图片放进去。
这是拜托緑青黒羽先生绘制的图像。非常感谢。(緑青黒羽先生的站点 点此进入)
这一次也是,先新建一个工程。如果觉得新建工程很麻烦的话,您可以直接使用教程附带的project中的项目,如果您不这么认为那么还是请自行新建工程。
接下来,这一次由于我们要处理图像,因此有必要先了解图像相关的知识。
有一种称为Bitmap文件(.bmp)的体积相当庞大的文件格式呢。
这是无压缩图像文件,数据被完完整整地写进文件。
因此虽然数据很庞大但是操作起来相当简单。
在DX Library中,颜色是用光的三原色的各自的亮度分量0~255来表示的。
Bitmap也是一样。每个像素都是红、蓝、绿三种颜色各自的亮度分量在0~255的范围内表示的。
也即是说,读入数据的话,
typedef struct{
unsigned char col[3];
}img_t;
把颜色数据放入这种感觉的结构体中似乎是个不错的想法。
不过,文件有一个叫做“文件头”的东西。
“这幅图像的尺寸是○X○哦。容量是怎么样的哦。规格又是什么哦。”
这样子的信息放在了数据的开头。这个文件头部分大小为54bit,之后才是图像本身的数据。
因此,读入的时候我们扔掉54bit的数据之后再读入就行了。
关于Bitmap的内容,在 这里有详细的说明。
另外,我们要决定子弹是否放置在那里,并没有必要使用0~255的亮度分量。
倒不如说,这里只需要分为“是否有颜色”2个种类即可。
这种方法称为“二值化“。为了进行二值化,我们将所有亮度分量的平均值分为128以上和128一下即可。
总之,在这一章中
1、读入
2、把颜色信息放入保存颜色的结构体中
3、进行二值化
4、绘制
那么我们来试着实现吧。
/* Bitmap图像的宽度 */
#define BMP_YOKO 400
/* Bitmap图像的高度 */
#define BMP_TATE 400
/* Bitmap的文件头大小 */
#define HEAD 54
/* 总共大小 */
#define TOTAL (BMP_YOKO*BMP_TATE*3+HEAD)
/* 作成AA数据合计子弹数 */
#define BULLET_MAX 4000
/* 用于Bitmap颜色信息存储的结构体 */
typedef struct{
unsigned char col[3];
}img_t;
/* TOTAL大小的数据 */
unsigned char data[TOTAL];
/* 图像的像素个数大小的颜色存储用结构体*/
img_t img[BMP_TATE][BMP_YOKO];
/* 存储二值化后的信息的数组*/
BYTE Pixel[BMP_TATE][BMP_YOKO];
总之我们先准备好这样一些变量和宏定义。
把图像设定为400×400。
TOTAL是包含文件头后的数据的比特大小。
BULLET_MAX现在暂时不使用。
/* 读入Bitmap并保存到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;
}
这样子就读入了Bitmap图像。这真是相当简单呢。
/* 将Bitmap的完整数据重新放入用于各个像素的颜色保存的结构体中 */
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结构体中。
/* 二值化 */
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,不满128的话(暗的话)为1
if( sum >= 128){
Pixel[y][x] = 0;
} else {
Pixel[y][x] = 1;
}
}
}
}
这样子就行了二值化。我们用sum来计算3个颜色分量的平均亮度,用这个平均亮度师傅在128以上来进行判断。
二值化后,保存到Pixel中的将会是这样:如果那里没有颜色(亮)为0,如果有颜色(暗)为1。
接下来就只是Pixel内容的绘制了。
那么我们来看看整个程序吧。
和鼠标、键盘相关的文件和上一章一样。
#include "../../../include/DxLib.h"
#include "Key_Mouse.h"
/* Bitmap图像宽度 */
#define BMP_YOKO 400
/* Bitmap图像高度 */
#define BMP_TATE 400
/* Bitmap文件头大小 */
#define HEAD 54
/* 总共大小 */
#define TOTAL (BMP_YOKO*BMP_TATE*3+HEAD)
/* 作成AA数据的总计子弹数 */
#define BULLET_MAX 4000
/* 保存Bitmap的颜色信息用的结构体 */
typedef struct{
unsigned char col[3];
}img_t;
int Key[256];
/* TOTAL大小的数据 */
unsigned char data[TOTAL];
/* 图像的像素个数大小的颜色存储用结构体*/
img_t img[BMP_TATE][BMP_YOKO];
/* 存储二值化后的信息的数组 */
BYTE Pixel[BMP_TATE][BMP_YOKO];
/* 读入Bitmap并保存到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;
}
/* 将Bitmap的完整数据重新放入用于各个像素的颜色保存的结构体中 */
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++;
}
}
}
}
/* 二值化 */
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,不满128的话(暗的话)为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();//读入Bitmap
ConvData();//保存到图像的颜色信息结构体中
Binarization();//二值化
while(ProcessMessage()==0/*消息处理*/ && ClearDrawScreen()==0 /*清空画面*/&& GetHitKeyStateAll_2(Key)==0 /*保存输入状态*/&& Key[KEY_INPUT_ESCAPE]==0/* ESC键没有被按下*/){
Graph();//绘制
ScreenFlip();
}
DxLib_End();
return 0;
}
运行结果
由于二值化后的Pixel数组的内容会作为进行DrawPixel的基础,因此应该可以理解从Biitmap图像中完整地读入数据然后变换得以进行的这一操作。