寫一個自己的C++控制台字符畫版BadApple!!


字符畫的原理十分簡單,就是先將圖片轉換成文本,然后在控制台輸出實現播放。

我寫的badapple程序:http://files.cnblogs.com/files/CodeMIRACLE/badapple.zip

首先下載 badapple!影繪.mp4

利用ffmpeg將這個視頻按照每秒8幀截取成24位位圖序列

ffmpeg -i badapple.mp4 -r 8 -vcodec bmp \p\%04d.bmp

在p文件夾下生成了1754個bmp文件,我們利用下面的程序把它們轉化為字符畫,並整合到一個txt文件中。

 1 #include <cstdio>
 2 #include <cstring>
 3 #include <stdint.h>
 4 #include <windows.h>
 5 int32_t width,height;
 6 RGBQUAD *pixels;
 7 bool OpenBitmap(char const *filename)
 8 {
 9 FILE *file = fopen(filename, "rb");
 10 if (file)
 11 {
 12 width=0;
 13 height=0;
 14 BITMAPFILEHEADER bf;
 15 BITMAPINFOHEADER bi;
 16 fread(&bf, sizeof(bf), 1, file);
 17 fread(&bi, sizeof(bi), 1, file);
 18 if(bi.biBitCount!=24)
 19 return false;
 20 if(bi.biCompression!=BI_RGB)
 21 return false;
 22 width=bi.biWidth;
 23 height=bi.biHeight;
 24 pixels=new RGBQUAD[width*height];
 25 uint32_t rowSize = (bi.biBitCount * width + 31) / 32 * 4;
 26 uint8_t *line = new uint8_t[rowSize];
 27 for (int y = 0; y < height; y++)
 28 {
 29 fread(line, rowSize, 1, file);
 30 for (int x = 0; x < width; x++)
 31 {
 32 uint8_t *color = line + x * 3;
 33 RGBQUAD *pixel = &pixels[(height-y-1) * width+x];
 34 pixel->rgbBlue = color[0];
 35 pixel->rgbGreen = color[1];
 36 pixel->rgbRed = color[2];
 37 }
 38 }
 39 delete[] line;
 40 fclose(file);
 41 return true;
 42 }
 43 return false;
 44 }
 45 RGBQUAD GetColor(int x, int y, int w, int h)
 46 {
 47 int r = 0, g = 0, b = 0;
 48 for (int i = 0; i < w; i++)
 49 {
 50 if (i + x >= width) continue;
 51 for (int j = 0; j < h; j++)
 52 {
 53 if (j + y >= height) continue;
 54 RGBQUAD const& color = pixels[(y + j) * width + (x + i)];
 55 r += color.rgbRed;
 56 g += color.rgbGreen;
 57 b += color.rgbBlue;
 58 }
 59 }
 60 return RGBQUAD{r / (w * h), g / (w * h),b / (w * h)};
 61 }
 62 char ColorToCharacter(RGBQUAD const& color)
 63 {
 64 int brightness = (color.rgbRed + color.rgbGreen + color.rgbBlue) / 3;
 65 static char const *characters = "Qdogc*;:-. ";
 66 int len = strlen(characters);
 67 int span = 0xFF / len;
 68 int cidx = brightness / span;
 69 if (cidx == len)
 70 cidx--;
 71 return characters[cidx];
 72 }
 73 void OutputAscii(const char* filename, int w, int h)
 74 {
 75 FILE *file=fopen(filename,"a+");
 76 int x = width / w;
 77 int y = height / h;
 78 for (int i = 0; i < height; i += y)
 79 {
 80 for (int j = 0; j < width; j += x)
 81 {
 82 RGBQUAD color = GetColor(j, i, x, y);
 83 fprintf(file, "%c", ColorToCharacter(color));
 84 //printf("%c", ColorToCharacter(color));
 85 }
 86 fprintf(file, "\n");
 87 //printf("\n");
 88 }
 89 delete [] pixels;
 90 fclose(file);
 91 }
 92 int main()
 93 {
 94 char filename[1024];
 95 for(int i=1;i<=1754;i++)
 96 {
 97 sprintf(filename,"p/%04d.bmp",i);
 98 if(OpenBitmap(filename));
 99 OutputAscii("badapple.txt",width/6,height/12);
100 }
101 }

 接下來要做的就是播放了,讀取文件然后輸出在控制台中。

為了保證和原來的視頻有一樣的播放速度,我們要限制其播放幀數。

 1 #include <cstdio>  2 #include <windows.h>  3 struct fps_limit {  4   5 int previous_time;  6 int tpf_limit;  7 int tpf;  8 fps_limit(int fps = 60) : previous_time(GetTickCount()), tpf(0) {  9 limit_fps(fps); 10 } 11 void reset() { 12 previous_time = GetTickCount(), 13 tpf = 0; 14 tpf_limit = 60; 15 } 16 void limit_fps(int fps) { 17 tpf_limit = (int)(1000.0f / (float)fps); 18 } 19 void delay() { 20 tpf = GetTickCount() - previous_time; 21  22 if(tpf < tpf_limit) 23 Sleep(tpf_limit - tpf - 1); 24  25 previous_time = GetTickCount(); 26 } 27 }; 28 int main() 29 { 30 FILE* fp=fopen("badapple.txt","r"); 31 char buf[1024]; 32 fps_limit fps(8); 33 while(!feof(fp)) 34 { 35 HANDLE hConsoleOutput = GetStdHandle(STD_OUTPUT_HANDLE); 36 COORD pos; 37 pos.X = 0; 38 pos.Y = 0; 39 SetConsoleCursorPosition(hConsoleOutput, pos); 40 for(int i=0;i<32;i++) 41 { 42 fgets(buf,1024,fp); 43 printf("%s",buf); 44 } 45 fps.delay(); 46 } 47 return 0; 48 }

 

 

到這位置badapple就寫完了 

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------

 

 

 

后來看見網上很多大神寫的badapple程序,他們的exe文件都特別小,只有單個exe文件。我開始考慮改進一下自己的程序。

先是壓縮數據,寫了個huffman壓縮,原來4.7M的badapple.txt文件,壓縮了成1.3M。感覺壓縮效率太低了。

肯定是我姿勢不對......

 1 #include<cstdio>  2 #include<vector>  3 #include<map>  4 #include<algorithm>  5 #include<queue>  6 #include<windows.h>  7 #include<string>  8 using namespace std;  9 int cnum[256]= {0};  10 map<char,string> huffmap;  11 static const unsigned char mask[8] =  12 {  13 0x80, /* 10000000 */  14 0x40, /* 01000000 */  15 0x20, /* 00100000 */  16 0x10, /* 00010000 */  17 0x08, /* 00001000 */  18 0x04, /* 00000100 */  19 0x02, /* 00000010 */  20 0x01 /* 00000001 */  21 };  22   23 typedef struct HTNode  24 {  25 char c;  26 unsigned int freq;  27 HTNode *lchild, *rchild;  28 HTNode(char key='\0', unsigned int fr=0, HTNode *l=NULL, HTNode *r=NULL):  29 c(key),freq(fr),lchild(l),rchild(r) {};  30 } HTNode,*pNode;  31   32 struct cmp  33 {  34 bool operator()(pNode node1, pNode node2)  35 {  36 return node1->freq > node2->freq;  37 }  38 };  39   40 priority_queue<pNode, vector<pNode>, cmp> pq;  41 void HuffmanCode(int n)  42 {  43 pNode l, r;  44 while (pq.size() > 1)  45 {  46 pNode z = new HTNode;  47 l = pq.top();  48 pq.pop();  49 r = pq.top();  50 pq.pop();  51 z->lchild = l;  52 z->rchild = r;  53 z->freq = l->freq + r->freq;  54 pq.push(z);  55 }  56 }  57   58 void GetCode(pNode t, string str)  59 {  60 if (t == NULL)  61 return;  62 if (t->lchild)  63 {  64 str += '0';  65 GetCode(t->lchild, str);  66 }  67 if (t->lchild == NULL && t->rchild == NULL)  68 {  69 //printf("%c's code: %s\n",t->c,str.c_str());  70 huffmap[t->c]=str;  71 }  72 str.erase(str.end()-1);  73 if (t->rchild)  74 {  75 str += '1';  76 GetCode(t->rchild, str);  77 }  78 }  79   80 void WriteCode(FILE *fp,string& code)  81 {  82 char bitBuf = 0x00;  83 int bitPos = 0;  84 for(int i = 0; i < code.size(); ++i)  85 {  86 if(code[i] == '1')  87 {  88 bitBuf |= mask[bitPos++];  89 }  90 else  91 {  92 bitPos++;  93 }  94 if(bitPos == 8)  95 {  96 fwrite(&bitBuf, 1, sizeof(bitBuf), fp);  97 bitBuf = 0x00;  98 bitPos = 0;  99 } 100 } 101 if(bitPos != 0) 102 { 103 fwrite(&bitBuf, 1, sizeof(bitBuf), fp); 104 } 105 } 106  107 int main() 108 { 109 FILE* fp=fopen("badapple.txt","r"); 110 while(!feof(fp)) 111 { 112 int c=fgetc(fp); 113 cnum[c]++; 114 } 115 int n=0; 116 for(int i=0; i<256; i++) 117 if(cnum[i]) 118 { 119 n++; 120 pNode p = new HTNode; 121 p->c = i; 122 p->freq = cnum[i]; 123 pq.push(p); 124 } 125 string str; 126 HuffmanCode(n); 127 GetCode(pq.top(), str); 128 FILE *fout=fopen("badapple.dat","wb+"); 129 fseek(fp,0,SEEK_SET); 130 str=""; 131 while(!feof(fp)) 132 { 133 int c=fgetc(fp); 134 str+=huffmap[c]; 135 } 136 fclose(fp); 137 WriteCode(fout,str); 138 fclose(fout); 139 }

 壓縮率好低啊, (╯‵□′)╯︵┴─┴ 

后來試了幾種其他方法,不想再試了,直接用別人的庫,后來用的zlib進行壓縮。

下面的程序把4.7M的文件,壓縮到了470多KB。

 1 #include <stdlib.h>  2 #include <string.h>  3 #include <cstdio>  4 #include "zlib.h"  5   6 int main()  7 {  8 FILE* fp=fopen("badapple.txt","r");  9 char* buf = NULL; 10 char* src = NULL; 11 fseek(fp,0,SEEK_END); 12 unsigned int flen=ftell(fp); 13 fseek(fp,0,SEEK_SET); 14 if((src = (char*)malloc(sizeof(char) * flen)) == NULL) 15 { 16 printf("no enough memory!\n"); 17 return -1; 18 } 19 fread(src,flen,sizeof(char),fp); 20 unsigned int blen=compressBound(flen); 21 if((buf = (char*)malloc(sizeof(char) * blen)) == NULL) 22 { 23 printf("no enough memory!\n"); 24 return -1; 25 } 26 if(compress(buf, &blen, src, flen) != Z_OK) 27 { 28 printf("compress failed!\n"); 29 return -1; 30 } 31 fclose(fp); 32 free(src); 33 fp=fopen("badapple.dat","wb"); 34 fwrite(&flen,1,sizeof(int),fp); 35 fwrite(buf,blen,sizeof(char),fp); 36 fclose(fp); 37 free(buf); 38 return 0; 39 }

 然后我寫了一個解壓程序測試一下。

 1 #include <cstdio>  2 #include <iostream>  3 #include <cstdlib>  4 #include "zlib.h"  5 using namespace std;  6 int main()  7 {  8 FILE* fp=fopen("badapple.dat","rb");  9 unsigned int slen; 10 fread(&slen,1,sizeof(int),fp); 11 char* buf = NULL; 12 char* dst = NULL; 13 fseek(fp,0,SEEK_END); 14 unsigned int flen=ftell(fp); 15 fseek(fp,4,SEEK_SET); 16 flen-=4; 17 unsigned int blen=compressBound(slen); 18 if((dst = (char*)malloc(sizeof(char) * slen)) == NULL) 19 { 20 printf("no enough memory!\n"); 21 return -1; 22 } 23 if((buf = (char*)malloc(sizeof(char) * blen)) == NULL) 24 { 25 printf("no enough memory!\n"); 26 return -1; 27 } 28 fread(buf,flen,sizeof(char),fp); 29 if(uncompress(dst, &slen, buf, blen) != Z_OK) 30 { 31 printf("uncompress failed!\n"); 32 return -1; 33 } 34 free(buf); 35 fclose(fp); 36 for(int i=0;i<slen;i++) 37 printf("%c",dst[i]); 38 }

 解壓和壓縮沒問題了,就該把數據文件打包生成exe文件了,有了圖像之后順便也把音樂加上吧。

先編寫rc文件

0 TYPEDATA "badapple.dat"
1 TYPEWAV "BadApple.wav"

調用windres生成res文件,然后和下面的程序鏈接生成exe文件

 1 #include <cstdio>  2 #include <windows.h>  3 #include "zlib.h"  4 char* dst = NULL;  5 struct fps_limit {  6   7 int previous_time;  8 int tpf_limit;  9 int tpf; 10 fps_limit(int fps = 60) : previous_time(GetTickCount()), tpf(0) { 11 limit_fps(fps); 12 } 13 void reset() { 14 previous_time = GetTickCount(), 15 tpf = 0; 16 tpf_limit = 60; 17 } 18 void limit_fps(int fps) { 19 tpf_limit = (int)(1000.0f / (float)fps); 20 } 21 void delay() { 22 tpf = GetTickCount() - previous_time; 23  24 if(tpf < tpf_limit) 25 Sleep(tpf_limit - tpf - 1); 26  27 previous_time = GetTickCount(); 28 } 29 }; 30 void Uncompressdata() 31 { 32 HRSRC hrscdat =FindResource(NULL,MAKEINTRESOURCE(0),"TYPEDATA"); 33 char *a=(char *)LockResource(LoadResource(NULL,hrscdat)); 34 DWORD flen = SizeofResource(NULL, hrscdat); 35 DWORD slen; 36 memcpy(&slen,a,sizeof(int)); 37 char* buf = NULL; 38 flen-=4; 39 unsigned int blen=compressBound(slen); 40 if((dst = (char*)malloc(sizeof(char) * (slen+1))) == NULL) 41 { 42 printf("no enough memory!\n"); 43 exit(1); 44 } 45 if((buf = (char*)malloc(sizeof(char) * blen)) == NULL) 46 { 47 printf("no enough memory!\n"); 48 exit(1); 49 } 50 memcpy(buf,a+4,flen*sizeof(char)); 51 if(uncompress(dst, &slen, buf, blen) != Z_OK) 52 { 53 printf("uncompress failed! %d\n"); 54 exit(1); 55 } 56 dst[slen]='\0'; 57 free(buf); 58 } 59 int main() 60 { 61 printf("Loading......"); 62 HRSRC hrscwav=FindResource(NULL,MAKEINTRESOURCE(1),"TYPEWAV"); 63 Uncompressdata(); 64 printf("OK\n把控制台的高度和寬度都調大點哦\n"); 65 system("PAUSE"); 66 system("CLS"); 67 PlaySound((LPCSTR)LockResource(LoadResource(NULL,hrscwav)),NULL,SND_MEMORY|SND_ASYNC); 68 fps_limit fps(8); 69 int i=0,j=0; 70 char buf[3000]; 71 Sleep(2000); 72 while(1) 73 { 74 HANDLE hConsoleOutput = GetStdHandle(STD_OUTPUT_HANDLE); 75 COORD pos; 76 pos.X = 0; 77 pos.Y = 0; 78 SetConsoleCursorPosition(hConsoleOutput, pos); 79 while(j<32*87) 80 { 81 if(dst[i]=='\0') 82 return 0; 83 buf[j++]=dst[i++]; 84 } 85 j=0; 86 printf("%s",buf); 87 fps.delay(); 88 } 89 }

 好了,大功告成,寫個這種程序還是蠻有意思的。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM