写一个自己的C++控制台字符画版BadApple!!

字符画的原理十分简单,就是先将图片转换成文本,然后在控制台输出实现播放。

我写的badapple程序:https://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  }

 好了,大功告成,写个这种程序还是蛮有意思的。

 

posted @ 2016-05-19 17:03  CodeMIRACLE  阅读(6659)  评论(2编辑  收藏  举报