您的位置:首页 > 编程语言 > C语言/C++

c++ 在控制台里用字符拼成图片

2015-06-14 08:38 573 查看
先上个效果图

原图



打印图



放大图



大体思路是这样的,不小心写了个死循环,看见屏幕上不停输出一行一行的乱序字母,然后想到,如果字母很多,能不能用不同的字母模拟像素点,然后写一个程序,读入一张图片,然后用各种字符把它在屏幕上拼出来.

不同的字符,不如说是16 x16 的点阵字 在相同的16 x16 中 每个字符所占的点阵中的点数是不一样的。




比如这个m 很显然比这个逗号 要“黑”很多。那我就可以用m表示比较暗的像素点,用逗号表示较亮的像素点。 如果把0~127 每个字符的明暗排序的话,我就得到了一组可以使用的像素点,然后读入我要画的图片,获取其像素点颜色信息。
和刚排好序的字符集 对应,然后按对应位置输出相对字符,就应该能大致描绘出图片了。

我没有接触过c/c++的图像处理,也不知道有什么处理图片的库什么的,反正我的要求也不高,只想获取像素点的信息而已, 不管怎样 先了解一下各种图片的存储格式吧。查了一圈,决定选择处理 BMP格式的图片,我们来看一下 它的结构



////////////////////////////////////////////////////////////////////////////////////////////////一下内容来自百科,大概了解下结构,找到我们要用的信息

BMP文件头(14字节)

3:位图信息头(40字节)

BMP位图信息头数据用于说明位图的尺寸等信息。

4:颜色表

颜色表用于说明位图中的颜色,它有若干个表项,每一个表项是一个RGBQUAD类型的结构,定义一种颜色。RGBQUAD结构的定义如下:

颜色表中RGBQUAD结构数据的个数有biBitCount来确定:

当biBitCount=1,4,8时,分别有2,16,256个表项;

当biBitCount=24时,没有颜色表项。

位图信息头和颜色表组成位图信息,BITMAPINFO结构定义如下:

5:位图数据

位图数据记录了位图的每一个像素值,记录顺序是在扫描行内是从左到右,扫描行之间是从下到上。位图的一个像素值所占的字节数:

当biBitCount=1时,8个像素占1个字节;

当biBitCount=4时,2个像素占1个字节;

当biBitCount=8时,1个像素占1个字节;

当biBitCount=24时,1个像素占3个字节,按顺序分别为B,G,R;
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

看完我找到了这些信息,

bmp图的头部有一些数据结构来描述图片的一些相关信息,有文件头,信息头,彩色表,数据。

其中文件头和信息头是固定长度的,根据图片的颜色类型不同,彩色表的长度不定,但在在文件头里有一个

bfOffBits 这个偏移量来标示真正的像素信息开始的位置,通过这个量就可以跳过头部信息,直接找到我们想要的像素信息。

还有一点要提的是,图片的色彩 深度,不确定是不是这么叫,简单理解就是支持多少颜色。在信息头里有一个

biBitCount
可以取值 1,4,8,16,24.比如说biBitCount =8 那表示 用8位表示一个像素点信息,也就是这个像素点 有0~255 中颜色可选,也就是说这是一个灰度图,通俗说 图片只有黑白灰颜色。

现在确定一下目标,我要处理 8位的 bmp图片, 因为灰度图 在黑框框里描绘 比较合适,而且每个像素点信息占一个字节也比较好处理~~~然后思路是这样的,程序读取一张bmp图,通过指针偏移控制,得到图片的

biWidth和

biHeight(乘积就是
所有像素点的个数,而且这两个量 在最后控制台输出的时候要做输出控制用),同样能得到bfOffBits, 通过这个量就能找到像素数据的起点。然后把数据都读出来。剩下的问题是 我如何把这个像素点信息,映射成最终要在屏幕上输出的字符。这里用一个数组[256]
(起个名字叫point),这个数组是这样来的,前边我们说过,要有一个字符集中各个字符“亮度”的一个排序, 把这些排好序的字符,对应到这个point[256]的数组中,数组每个元素存储的是对应字符的ASCII码。

然后我从图片中读取一个像素的信息,其实就是一个字节,就是一个0~255
的数字,这个数字num代表这个像素点在原图中的颜色,在8位灰度图中就理解为亮度,假设num= 255 那这个像素点在原图中是“最亮”的,即白色, 那么

point[num]=
就对应字符集中相对“最亮的”的那个字符的ASCII码。其他的不同亮度也能按下标一一对应,因为

point[]
已经按亮度排好序了。这时候想在屏幕上输出这个点 可以

cout<<(char)piont[num];

这样就找到了像素点所对应的字符。

很重要的一个问题是,怎么获得字符集中128个字符的“亮度”排序? 我实在没有找到什么好办法。。。 找了一张

ASCII码表 切成了 128张bmp。。。



然后写了个程序,通过上边对bmp文件格式的分析,读出每个bmp像素信息,统计黑像素点个数,然后排序,将结果保存。。。

这其中有几点,

128个字符 中黑色像素点个数 有很多字符是相同,也就是这些字符打在屏幕上“亮度”是,一样的。还有一些字符是不可打印的,考虑到上述因素,最后筛选出可以用字符集“亮度”序列,有64
个字符,然后让每一个字符 对应 0~255中的四个亮度,形式point[255],保存到文件。

获取point[]的程序 getdata.cpp

#include<iostream>
#include<map>
#include<string>
#include<windows.h>
#include<vector>
#include<algorithm>
using namespace std;

//get length of hearder
void bmpHeaderPartLength(FILE* fpbmp);
// get width and height
void BmpWidthHeight(FILE* fpbmp);
//save letter's black pixel number into a map
void SaveLetterInfo(FILE* fpbmp,int asc_code);

unsigned int OffSet = 0;    // OffSet from Header part to Data Part
long BmpWidth = 0;
long BmpHeight = 0;
int point[256];
map<int,int > ma;

//get header length(offset)
void bmpHeaderPartLength(FILE* fpbmp)
{
fseek(fpbmp, 10, SEEK_SET);
fread(&OffSet, sizeof(char), 4, fpbmp);
}

//get width and height
void BmpWidthHeight(FILE* fpbmp)
{
fseek(fpbmp, 18, SEEK_SET);
fread(&BmpWidth, sizeof(char), 4, fpbmp);
fread(&BmpHeight, sizeof(char), 4, fpbmp);
}

// save letter's black pixel number into a map
void SaveLetterInfo(FILE* fpbmp,int asc_code)
{

unsigned char* bmpPixel= NULL;
unsigned char* tem = NULL;
int numOfPixel = 0;
fseek(fpbmp, OffSet, SEEK_SET);
if ((bmpPixel=(unsigned char*)malloc(sizeof(char)*BmpWidth*BmpHeight))==NULL)
{
printf("allocation failed.!!!\n");
exit(1);
}
tem = bmpPixel;
fread(bmpPixel, sizeof(char), BmpWidth*BmpHeight, fpbmp);

for(int i=0;i<BmpWidth;++i)
{
for(int j=0;j<BmpHeight;++j)
{
if(*bmpPixel < 100)
numOfPixel++;
bmpPixel++;
}
}
ma.insert(pair<int,int>(numOfPixel,asc_code));
free(tem);
}
int main(int argc, char* argv[])
{

string s_fileName;
char fileNamehead[4];

for(int i=0;i<128;++i)
{
sprintf(fileNamehead,"%d",i);
s_fileName.erase();
s_fileName += fileNamehead;
s_fileName += ".bmp";
FILE *fpbmp = fopen(s_fileName.c_str(), "r+");
if (fpbmp == NULL)
{
printf("Open bmp failed!!!\n");
return 1;
}
bmpHeaderPartLength(fpbmp);
BmpWidthHeight(fpbmp);
SaveLetterInfo(fpbmp,i);
fclose(fpbmp);
}

//erase the character which cant be printed
ma.erase(22);
ma.erase(4);
ma.erase(41);
ma.erase(34);
ma.erase(42);
ma.erase(54);
ma.erase(63);
ma.erase(70);
ma.erase(94);
ma.erase(99);
ma.erase(115);
ma.erase(6);//22  4 41 34 42 54 63 70 94 99 115 6
cout<<"map size ="<<ma.size()<<endl;
map<int,int>::iterator it = ma.begin();
////////////////////////for test    to find witch charcter to erase
// 		int k =0;
// 	 	for(it=ma.begin();it!=ma.end();++it)
// 	 	{
// 	 		cout<<(char)(it->second)<<" "<<(it->second)<<" "<<it->first<<"---------"<<k++<<endl;
// 	 	}
for( int k=0;k<256;k+=4)
{
point[k] = it->second;
if(k < 255)
{
point[k+1] = it->second;
point[k+2] = it->second;
point[k+3] = it->second;
}
it++;
}
FILE *fpbmp = fopen("data.txt", "w");
if (fpbmp == NULL)
{
printf("Open data failed!!!\n");
return 1;
}
for(int m =0;m<256;++m)
{
fprintf(fpbmp,"%d ",point[m]);
}

}


绘图程序 bmp.cpp

//...没有整理公共头文件

#include<iostream>
#include<map>
#include<string>
#include<windows.h>
using namespace std;

//get length of hearder
void bmpHeaderPartLength(FILE* fpbmp);
// get width and height
void BmpWidthHeight(FILE* fpbmp);
//save letter's black pixel number into a map
void SaveLetterInfo(FILE* fpbmp,int asc_code);

//draw the bmp on screen with charcter
void bmpOut(FILE* fpbmp);

unsigned int OffSet = 0;    // OffSet from Header part to Data Part
long BmpWidth = 0;          // The Width of the Data Part
long BmpHeight = 0;         // The Height of the Data Part
int point[256];

//get header length(offset)
void bmpHeaderPartLength(FILE* fpbmp)
{
fseek(fpbmp, 10, SEEK_SET);
fread(&OffSet, sizeof(char), 4, fpbmp);
//printf("The Header Part is of length %d.\n", OffSet);
}

//get width and height
void BmpWidthHeight(FILE* fpbmp)
{
fseek(fpbmp, 18, SEEK_SET);
fread(&BmpWidth, sizeof(char), 4, fpbmp);
fread(&BmpHeight, sizeof(char), 4, fpbmp);
}

void bmpOut(FILE* fpbmp)
{
unsigned char* bmpPixelTmp = NULL;
unsigned char* tem ;
unsigned char * charcterToPrint = NULL;
fseek(fpbmp, OffSet, SEEK_SET);

if ((bmpPixelTmp=(unsigned char*)malloc(sizeof(char)*BmpWidth*BmpHeight))==NULL)
{
printf("Data allocation failed.!!!\n");
exit(1);
}
charcterToPrint=(unsigned char*)malloc(sizeof(char)*BmpWidth*BmpHeight +1+ BmpHeight);
if (charcterToPrint == NULL)
{
printf("allocation failed.!!!\n");
exit(1);
}
int index = 0;
tem = bmpPixelTmp;
fread(bmpPixelTmp, sizeof(char), BmpWidth*BmpHeight, fpbmp);
bmpPixelTmp += BmpHeight * BmpWidth -1;
///////////////////////////////////////////////////////////////////////////////////

bmpPixelTmp = bmpPixelTmp - BmpWidth +1;
for(int i=0;i<BmpHeight;++i)
{
<span style="white-space:pre">	</span>for(int j=0;j<BmpWidth;++j)
{
charcterToPrint[index++] = (char)point[*bmpPixelTmp];
bmpPixelTmp++;
}
charcterToPrint[index++] = '\n';
bmpPixelTmp = bmpPixelTmp - 2*BmpWidth;

}
charcterToPrint[index] = '\0';
cout <<charcterToPrint<<endl;

free(tem);
free(charcterToPrint);
}
int main()
{

FILE *fpdata = fopen("data.txt", "r+");
if (fpdata == NULL)
{
printf("Open data failed!!!%d\n");
return 1;
}

for(int i=0;i<256;++i)
{
fscanf(fpdata,"%d ",&point[i]);
}
<span style="white-space:pre">	</span>// open the picture and draw it!
FILE *fpbmp = fopen("filename.bmp", "r+");
if (fpbmp == NULL)
{
printf("Open mbp failed!!!\n");
return 1;
}

bmpHeaderPartLength(fpbmp);
BmpWidthHeight(fpbmp);
bmpOut(fpbmp);

return 0;
}
这里边有个坑,开始的时候我读一个像素点就打印一个字符,结果一个300 x300 的图片 要打印十万次,十万次的陷入内核去输出,程序在我哦电脑上一跑就卡死了。。。
后来自己先写个char* 缓冲下,只输出一次,就能很流畅的跑了

这个程序处理那些 黑白分明的图片效果比较好,毕竟用字符描绘 能力有限。

再上几张图





内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: