字符畫的原理十分簡單,就是先將圖片轉換成文本,然后在控制台輸出實現播放。
我寫的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 }
好了,大功告成,寫個這種程序還是蠻有意思的。