• 写一个自己的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, "
    ");
     87          //printf("
    ");
     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='', 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
    ",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!
    ");
    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!
    ");
    24          return -1;
    25      }
    26      if(compress(buf, &blen, src, flen) != Z_OK)
    27      {
    28          printf("compress failed!
    ");
    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!
    ");
    21          return -1;
    22      }
    23      if((buf = (char*)malloc(sizeof(char) * blen)) == NULL)
    24      {
    25          printf("no enough memory!
    ");
    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!
    ");
    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!
    ");
    43          exit(1);
    44      }
    45      if((buf = (char*)malloc(sizeof(char) * blen)) == NULL)
    46      {
    47          printf("no enough memory!
    ");
    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
    ");
    54          exit(1);
    55      }
    56      dst[slen]='';
    57      free(buf);
    58  }
    59  int main()
    60  {
    61      printf("Loading......");
    62      HRSRC hrscwav=FindResource(NULL,MAKEINTRESOURCE(1),"TYPEWAV");
    63      Uncompressdata();
    64      printf("OK
    把控制台的高度和宽度都调大点哦
    ");
    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]=='')
    82                  return 0;
    83              buf[j++]=dst[i++];
    84         }
    85          j=0;
    86          printf("%s",buf);
    87          fps.delay();
    88      }
    89  }
    

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

  • 相关阅读:
    记录排序算法
    Redis 记录
    ELK Windows环境 强行记录
    前端组件 bootstrap-select 下拉 多选 搜索
    记一次微信点赞小网站的事故
    来自加班的吐槽
    .net 比较器
    做一个.net core 小项目 遇到的一些坑
    即使通讯架构
    resultMap 映射
  • 原文地址:https://www.cnblogs.com/CodeMIRACLE/p/5508236.html
Copyright © 2020-2023  润新知