第五十五章 图像→弹幕 试着制作变换工具吧(2)
那么我们来完成在54章中制作的画像→弹幕的变换工具吧。
这一次我们应该追加的处理就是“放置子弹”的处理了。
以决定好了的间隔,在线上放置子弹。
将全部的像素从头至尾地遍历一次,当某个像素上有颜色的话那么再考虑是否放置子弹。
首先,计算所有当前已经登录了的子弹和当前位置位置的距离,如果所有子弹和当前位置的距离都在已经决定好了的距离以上的话,那么就可以在那里放置子弹了。
这样一来,在一定的距离以内子弹之间不会重叠,然后再在线上放置子弹吧。
另外,我们还要事先让子弹放置的间隔可以用左右键来调整。
进一步,我们也加上通过按下空格键,能在“只显示子弹”、“显示线画和子弹”、“只显示线画”这样子的显示方法之间进行变换的功能。 总之,我们来看看代码吧。
首先我们定义子弹的结构体。
//子弹的坐标(55)
typedef struct{
float x,y;
}Pt_t;
//放置了的子弹的结构体
typedef struct{
int num;//现在已经放置了的子弹数
Pt_t Pt[ BULLET_MAX ];
}Bl_t;
然后定义坐标的结构体Pt_t以及和与集中了BULLET_MAX个放置好了的子弹相关的结构体Bl_t。
typedef struct{
int state; //状态
double len; //间隔
}Operate_t;
Bl_t Bl;
Operate_t Operate;
这一次也定义和操作相关的结构体Operate_t。其中它的成员有和显示方法相关的.state以及和子弹放置的距离相关的.len。
int img_bullet[4] ;
int img_back;
因为我们使用了图像,因此我们定义保存图像句柄用的变量。
/* 子弹放置计算 */
void CalcPut(){
int i,x,y;
double lx,ly,len;
//置当前登录了的子弹个数为0个
Bl.num = 0;
//Bitmap图像大小次循环
for(y=0;y<BMP_TATE;y++){
for(x=0;x<BMP_YOKO;x++){
//如果子弹已经无法登录了那么跳出西循环
if( Bl.num >= BULLET_MAX-1 ){
break;
}
//如果当前位置有颜色
if( Pixel[y][x] == 1){
//当前登录了的子弹个数次循环
for(i=0; i<Bl.num; i++){
//所有的子弹和当前位置的距离在Operate.len以上的话不跳出循环
lx = x - Bl.Pt[i].x;
ly = y - Bl.Pt[i].y;
if( lx*lx + ly*ly < Operate.len*Operate.len){
break;
}
}
//没有中途放弃=没有一个子弹在附近的的话
if( i == Bl.num ){
//登录
Bl.Pt[ Bl.num ].x = x;
Bl.Pt[ Bl.num ].y = y;
Bl.num++;
}
}
}
}
}
我们从头开始检查图像。
如果找到有颜色的像素点,那么考虑是否在那里放置子弹。
计算当前位置和当前已经登录的所有子弹的距离,如果和任何子弹的距离都在规定间隔以上的话就在那个地方登录子弹。
/* 操作计算 */
void CalcOperate(){
//每按下Space键就变化一次状态
if( Key[KEY_INPUT_SPACE] == 1 ){
Operate.state = ( Operate.state+1 )%3;
}
//如果左方向键被按下则减少.len
if( Key[KEY_INPUT_LEFT] == 1 || Key[KEY_INPUT_LEFT] > 20 ){
if( Operate.len > 1 ){
Operate.len -= 0.2;
}
CalcPut();
}
//如果右方向键被按下则增加.len
if( Key[KEY_INPUT_RIGHT] == 1 || Key[KEY_INPUT_RIGHT] > 20 ){
if( Operate.len < 50 ){
Operate.len += 0.2;
}
CalcPut();
}
}
我们这样子计算操作。每次按下Space键则变更显示状态。
另外,每按下左右方向键能够变更规定间隔。一直按下能够快速计算。
/* 绘制 */
void Graph(){
int i;
//绘制模式随状态的变化而变化
if( Operate.state == 1 || Operate.state == 2 ){
DrawGraph(0,0,img_back,FALSE);
}
if( Operate.state == 0 || Operate.state == 1 ){
for(i=0; i<Bl.num; i++){
DrawRotaGraph(Bl.Pt[i].x,Bl.Pt[i].y,1.0,0.0,img_bullet[1],TRUE);
}
}
SetDrawBlendMode( DX_BLENDMODE_ALPHA , 64 ) ;
DrawBox(0,0,100,40,0,TRUE);
SetDrawBlendMode( DX_BLENDMODE_NOBLEND , 0 ) ;
DrawFormatString(0,0,GetColor(255,255,255),"间隔=%.1f",Operate.len);
DrawFormatString(0,20,GetColor(255,255,255),"子弹数=%d",Bl.num);
}
根据操作状态的不同,可以分为只显示线画、只显示子弹等显示状态。
我们也显示了当前已经登录的子弹数和间隔。
//写出数据
void Output(){
int i;
FILE *fp;
fp = fopen( "Output.dat" , "wb" );
for(i=0; i<Bl.num; i++){//把坐标数据变换到-1~1之间
Bl.Pt[i].x -= BMP_YOKO/2;
Bl.Pt[i].y -= BMP_TATE/2;
Bl.Pt[i].x /= BMP_YOKO/2;
Bl.Pt[i].y /= BMP_TATE/2;
}
if( fp == NULL ){
return;
}
fwrite( &Bl, sizeof(Bl_t), 1, fp );
fclose(fp);
}
和数据写出相关的东西和前一章相同。 那么我们来试着看一下整个源码吧。
— main.cpp —
#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];
//子弹的坐标(55)
typedef struct{
float x,y;
}Pt_t;
//放置的子弹的结构体
typedef struct{
int num;//当前放置了的子弹数
Pt_t Pt[ BULLET_MAX ];
}Bl_t;
typedef struct{
int state; //状态
double len; //间隔
}Operate_t;
Bl_t Bl;
Operate_t Operate;
/* TOTAL大小的数据 */
unsigned char data[TOTAL];
/* 图像的像素个数大小的颜色存储用结构体*/
img_t img[BMP_TATE][BMP_YOKO];
/* 存储二值化后的信息的数组 */
BYTE Pixel[BMP_TATE][BMP_YOKO];
int img_bullet[4] ; //(55)
int img_back;
/* 读入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 CalcPut(){
int i,x,y;
double lx,ly,len;
//置当前登录了的子弹个数为0个
Bl.num = 0;
//Bitmap图像大小次循环
for(y=0;y<BMP_TATE;y++){
for(x=0;x<BMP_YOKO;x++){
//如果子弹已经无法登录了那么跳出西循环
if( Bl.num >= BULLET_MAX-1 ){
break;
}
//如果当前位置有颜色
if( Pixel[y][x] == 1){
//当前登录了的子弹个数次循环
for(i=0; i<Bl.num; i++){
//所有的子弹和当前位置的距离在Operate.len以上的话不跳出循环
lx = x - Bl.Pt[i].x;
ly = y - Bl.Pt[i].y;
if( lx*lx + ly*ly < Operate.len*Operate.len){
break;
}
}
//没有中途放弃=没有一个子弹在附近的的话
if( i == Bl.num ){
//登录
Bl.Pt[ Bl.num ].x = x;
Bl.Pt[ Bl.num ].y = y;
Bl.num++;
}
}
}
}
}
/* 操作计算 */
void CalcOperate(){
//每按下Space键就变化一次状态
if( Key[KEY_INPUT_SPACE] == 1 ){
Operate.state = ( Operate.state+1 )%3;
}
//如果左方向键被按下则减少.len
if( Key[KEY_INPUT_LEFT] == 1 || Key[KEY_INPUT_LEFT] > 20 ){
if( Operate.len > 1 ){
Operate.len -= 0.2;
}
CalcPut();
}
//如果右方向键被按下则增加.len
if( Key[KEY_INPUT_RIGHT] == 1 || Key[KEY_INPUT_RIGHT] > 20 ){
if( Operate.len < 50 ){
Operate.len += 0.2;
}
CalcPut();
}
}
/* 绘制 */
void Graph(){
int i;
//绘制模式随状态的变化而变化
if( Operate.state == 1 || Operate.state == 2 ){
DrawGraph(0,0,img_back,FALSE);
}
if( Operate.state == 0 || Operate.state == 1 ){
for(i=0; i<Bl.num; i++){
DrawRotaGraph(Bl.Pt[i].x,Bl.Pt[i].y,1.0,0.0,img_bullet[1],TRUE);
}
}
SetDrawBlendMode( DX_BLENDMODE_ALPHA , 64 ) ;
DrawBox(0,0,100,40,0,TRUE);
SetDrawBlendMode( DX_BLENDMODE_NOBLEND , 0 ) ;
DrawFormatString(0,0,GetColor(255,255,255),"间隔=%.1f",Operate.len);
DrawFormatString(0,20,GetColor(255,255,255),"子弹数=%d",Bl.num);
}
//写出数据
void Output(){
int i;
FILE *fp;
fp = fopen( "Output.dat" , "wb" );
for(i=0; i<Bl.num; i++){//把坐标数据变换到-1~1之间
Bl.Pt[i].x -= BMP_YOKO/2;
Bl.Pt[i].y -= BMP_TATE/2;
Bl.Pt[i].x /= BMP_YOKO/2;
Bl.Pt[i].y /= BMP_TATE/2;
}
if( fp == NULL ){
return;
}
fwrite( &Bl, sizeof(Bl_t), 1, fp );
fclose(fp);
}
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;//初始化和里画面化
Operate.len = 5;//设置子弹间的间隔为5
img_back = LoadGraph( "../AA画像/aisha.bmp" );
LoadDivGraph( "../dat/img/bullet/14.png" , 4 , 4 , 1 , 6 , 6 , img_bullet ) ;//读入子弹(55)
ReadBmp();//读入Bitmap
ConvData();//保存到图像的颜色信息结构体中
Binarization();//二值化
CalcPut();//子弹放置计算
while(ProcessMessage()==0/*消息处理*/ && ClearDrawScreen()==0 /*清空画面*/&& GetHitKeyStateAll_2(Key)==0 /*保存输入状态*/&& Key[KEY_INPUT_ESCAPE]==0/* ESC键没有被按下*/){
CalcOperate();//操作计算
Graph();//绘制
ScreenFlip();
}
Output();//写出文件
DxLib_End();
return 0;
}
运行结果