前言:记得10年的时候我还在上学,有一天逛csdn看到了字符版的badapple,感觉这东西好NB啊,然后就下载了一份,最近整理博客就把他整理博客,原作者是谁真心不知道,这是在果壳看到的.

Bad Apple最初是作为日本同人游戏社团“上海爱莉丝幻乐团”所制作的“东方Project”系列游戏背景音乐出现的。后来在日本著名视频站点NicoVideo上有人萌发了为此系列制作MADMovie的想法,最后有了这段3D制作的影绘风格视频:

这段视频由不断变换的影绘风格游戏人物组成,完全黑白,视觉震撼力十足。此视频在网络上引发了一场宏大的运动,各种技术宅使用了各种方法来演奏这段三分多钟长的视频。

看完本文后,你将能做出如下效果的视频:

 

1   所需工具

● Visual Studio 2010
● KMPlayer 3.0
 

2   思路原理

● 我们想要做的就是在Windows控制台(也就是命令提示符,俗称“DOS窗口”)下播放Bad Apple视频,视频的基本原理就是利用人眼的视觉暂留效果,快速在控制台输出字符-清屏-输出字符-清屏,达到“动画”的效果。

而输出的字符需要从原始视频获取,将原始视频压缩成一个较低的分辨率,再获取到每一帧上的所有像素点,转换成对应的字符串。

 

3   确定思路

● 首先要选定使用的语言,这个根据大家的爱好,随便C/C++、Java、Python等等绝大部分编程语言都可以实现,这里选用的是C#。
● 关于素材的来源,这个是难度最大的步骤。原始1080P视频素材在网上很容易下到,但要处理成我们需要的字符串形式。也就是在视频黑色像素的位置输出一个字符,白色像素的位置输出为空格,每一帧都依据像素排列位置输出成这种形式,如图:

 
● 获取字符图像大概有两个思路:
1. 根据视频格式(我下到的是H264编码方式的),读取二进制文件并进行转码;
2. 使用第三方工具或者类库,将视频的每一帧转换为图片格式,再将图片转换为所需字符串形式。
● 第一种方式需要研究视频编码的问题,而且比较复杂,因此我们选择取巧的方式。KMPlayer是个很好的视频播放软件,它的截图功能非常强大,可以将视频每一帧图像都保存为截图,完全能够满足我们的需要。
 

4   捕获图像

● 使用KMPlayer打开原始视频,点击右键选择“捕获”->“高级捕获”(如果没有捕获项,请勾选“设置”->“高级菜单”项),就可以了。
● 需要考虑的是分辨率与帧数的问题,Windows7下1366×768分辨率默认命令提示符窗口最大一行显示80个字符,最大44列,因此我们截取的视频分辨率应该小于等于这个值。当然还可以在后面过程中在调整分辨率,但要设计插值计算的问题,因此还是交给KMPlayer比较轻松一些。
● 另外命令提示符每个字符的上下间距与左右间距是不同的,上下间距要高上许多,如果按照视频原始宽高比截图的话最后输出的画面会有上下拉伸现象,因此截图的时候就要故意把视频截的“胖”一些。
● 最后综合考虑,决定一秒截25帧图像,图像分辨率为80×30。

 
● 这时还可以顺便将音频也用KMPlayer截取下来,以便保证视频与音频的同步(如果再如此折腾下还没有对此歌产生审美疲劳的话还可以丢进MP3里听)。音频比较简单,从头到尾录制,保存为mp3或者其他常见格式就可以,只要你使用的语言有可用的将它播放出来的库就可以。
 
● 截取的图片一共有3553张,保存在一个文件夹中。

● 其实这个时候就有一个简单的方法将这些静态图片播放出来了,只要使用Picasa附带的图片查看器打开第一帧图片,鼠标点中下一张,如何,动起来了吧?其他能够点种下一张不放就可以连续切换图片的查看器也能实现同样的效果。
 

5   编码

● 下面就可以开始编码了,思路很容易,依次读取每一张图片,遍历图片上每一个像素点,如果是黑色的话输出一个字符(这里用#字符,因为#块头比较大并且用的是C#),白色的话输出一个空格,将字符串保存在一个文本文件里。
● 仔细观察保存的图片时会发现一个问题,Bad Apple视频里并不是绝对的黑色与白色,还有少量的灰色作为渐变色彩,因此判断黑白的时候还是根据RGB值,只要其中一项大于200便当作黑色处理就好了。
● 第一次保存的时候发现数据量比较大,未压缩大概有将近10M大小,有些难以接受。因为保存的文本大部分字符都是重复的,因此压缩空间应该非常大,于是使用.net自带的Gzip压缩了一下,果然文件大小降至517KB了。
● 此时还可以用另外一种方法播放这段“文本视频”,只要用记事本打开数据文件,调整记事本窗口的宽度与高度(宽度大于一行80个字符,高度刚好每行30个字符),然后按下PageDown按钮,看到效果了吧~!
 

6   输出

● 剩下的工作就比较简单,只要读取这个文件,并在控制台不停的输出,每次输出30行(因为横向分辨率为30),然后清屏,继续输出。只要设定好每次输出时间的间隔,与截屏的间隔相符的话,动画就可以顺利的在控制台播放出来了。
● 只要仔细调节截屏的帧数、分辨率,控制台的宽度与高度、输出时间的间隔,就一定可以顺利播放出来,当然这个过程比较繁琐。
● 最后给程序添加上背景音乐,这里使用第三方的NAudio库来播放我们一开始截取的音频文件,只要你之前调节的参数没有问题,音频应该和动画达到神同步的境界的。让我们随着Bad Apple的旋律哼唱着“多少红颜为傻逼 多少傻逼不珍惜”来运行我们的程序吧!

 

7   源码预览

     class Program
{
static void Main(string[] args)
{
//获取in目录下所有文件 未作异常处理
string[] path = Directory.GetFileSystemEntries("in"); //保存为Gzip压缩的文本流
FileStream fileStream = new FileStream("badapple.dat", FileMode.Create, FileAccess.Write);
GZipStream compressionStream = new GZipStream(fileStream, CompressionMode.Compress);
StreamWriter sw = new StreamWriter(compressionStream); //遍历每个像素点
for (int x=;x<path.Length;x++)
{
//Console.WriteLine(path[x]);
Bitmap bitmap = new Bitmap(path[x]); for (int i = ; i < bitmap.Height; i++)
{
for (int j = ; j < bitmap.Width; j++)
{
Color color = bitmap.GetPixel(j, i);
if (color.R > )
{
//Console.Write("1");
sw.Write(" ");
}
else
{
//Console.Write("0");
sw.Write("#");
}
}
//Console.WriteLine();
sw.WriteLine();
Console.Clear();
Console.WriteLine("{0} of {1} completed! ",x,path.Length);
}
bitmap.Dispose();
}
sw.Dispose();
} }

压缩图片

    class Program
{
private static StreamReader reader;
static void Main(string[] args)
{
//打开压缩文本流
FileStream fileStream = new FileStream("badapple.dat", FileMode.Open, FileAccess.Read);
GZipStream compressionStream = new GZipStream(fileStream, CompressionMode.Decompress);
reader = new StreamReader(compressionStream); //初始化控制台参数
Console.Title = "BadAppleSharp 0.1";
Console.WindowWidth = ;
Console.WindowHeight = ; //调用播放器播放背景音乐
WaveStream mp3Reader = new Mp3FileReader(@"BadApple.mp3");
WaveStream pcmStreamer = WaveFormatConversionStream.CreatePcmStream(mp3Reader);
WaveStream blockAlignedStream = new BlockAlignReductionStream(pcmStreamer);
WaveOut waveOut = new WaveOut(WaveCallbackInfo.FunctionCallback());
waveOut.Init(blockAlignedStream);
waveOut.Play(); //初始化定时器
Timer timer = new Timer();
timer.Elapsed+=new ElapsedEventHandler(Render);
timer.Enabled = true; Console.ReadKey();
waveOut.Dispose();
blockAlignedStream.Dispose();
pcmStreamer.Dispose();
mp3Reader.Dispose();
} //渲染一帧
public static void Render(Object sender,ElapsedEventArgs e)
{
{
Console.Clear();
StringBuilder data=new StringBuilder();
for (int i = ; i < ; i++)
{
data.Append(reader.ReadLine());
}
Console.Write(data.ToString());
} }
}

播放源码

8   源码下载

程序及源代码下载 (需要本机安装.net2.0或以上运行环境)

转载自果壳艾斯昆

Bad apple for CSharp的更多相关文章

  1. Storm系列(二):使用Csharp创建你的第一个Storm拓扑(wordcount)

    WordCount在大数据领域就像学习一门语言时的hello world,得益于Storm的开源以及Storm.Net.Adapter,现在我们也可以像Java或Python一样,使用Csharp创建 ...

  2. c#操作MangoDB 之MangoDB CSharp Driver驱动详解

    序言 MangoDB CSharp Driver是c#操作mongodb的官方驱动. 官方Api文档:http://api.mongodb.org/csharp/2.2/html/R_Project_ ...

  3. iOS: 在iPhone和Apple Watch之间共享数据: App Groups

    我们可以在iPhone和Apple Watch间通过app groups来共享数据.方法如下: 首先要在dev center添加一个新的 app group: 接下来创建一个新的single view ...

  4. c#进阶之神奇的CSharp

    CSharp 简写为c#,是一门非常年轻而又有活力的语言. CSharp的诞生      在2000年6月微软发布了c#这门新的语言.作为微软公司.NET 平台的主角,c#吸收了在他之前诞生的语言(c ...

  5. 一道Apple公司(中国)的面试题目

    Apple在中国(上海)有公司业务,但是感觉主要是做测试工作的部门,主要是保障Apple的产品质量QE.面试的时候,面试官出了一道题目,我貌似曾今开过类似的题目,但是由于当场发挥不佳没有答出来.题目大 ...

  6. apple常用网址

    https://developer.apple.com/ https://itunesconnect.apple.com/ iTunes Connect Developer Guide https:/ ...

  7. Apple Pay 初探

    Apple Pay 一.概述 1.支付方式:Touch ID/ Passcode 2.设备要求:iPhone6以上(iphone:线上/线下 ipad:线上 watch:线下) 3.系统要求:iOS8 ...

  8. Apple、Google、Microsoft的用户体验设计原则

    轻巧的Apple 注重设计过程: 在设计过程中引入用户交互的5个目标: 了解您的目标客户 分析用户的工作流 构造原型系统 观察用户测试 制定观察用户准则 做出设计决定 避免功能泛滥 80% 方案 优秀 ...

  9. 【转】iOS开发 -- Apple Pay

    技术博客原地址:http://www.cnblogs.com/dashunzi/p/ApplePay.html#top 原技术博客中有源码和视频,有感兴趣的朋友可以研究一下! 一.什么是Apple P ...

随机推荐

  1. HDU 4899 Hero meet devil(状压DP)(2014 Multi-University Training Contest 4)

    Problem Description There is an old country and the king fell in love with a devil. The devil always ...

  2. CCF真题之门禁系统

    201412-1 问题描述 涛涛最近要负责图书馆的管理工作,需要记录下每天读者的到访情况.每位读者有一个编号,每条记录用读者的编号来表示.给出读者的来访记录,请问每一条记录中的读者是第几次出现. 输入 ...

  3. mac工具-解析json visualJSON和JSON Accelerator这两款工具

  4. redis连接数问题

    redis连接数查看 info client redis连接数满了,不会继续建立连接. 造成redis连接数满了原因有很多. 1.建立新连接不close()的话redis连接不会回归连接池. 显示所有 ...

  5. oracle ebs request一直pending

    如果提交请求以后,状态一直是pending状态,可以在“工具”打开“Manager”,查看一下Maximum是否有设置错,另外pending的数量当前是多少. 如果Maximum是1,pending是 ...

  6. Deep Learning 深度学习 学习教程网站集锦

    http://blog.sciencenet.cn/blog-517721-852551.html 学习笔记:深度学习是机器学习的突破 2006-2007年,加拿大多伦多大学教授.机器学习领域的泰斗G ...

  7. TreeNode动态邦定事件

    1. 显示不能用TextField,应该用StaticText 2. public void SetNodesAction(Tree t, String methodbind){ ArrayList ...

  8. eclispse快捷操作

    1几个最重要的快捷键 代码助手:Ctrl+Space(简体中文操作系统是Alt+/)快速修正:Ctrl+1单词补全:Alt+/打开外部Java文档:Shift+F2 显示搜索对话框:Ctrl+H快速O ...

  9. MySQL对于数据库应该如何如何配置安全问题了

    mysql 是完全网络化的跨平台关系型数据库系统,同时是具有客户机/服务器体系结构的分布式数据库管理系统.它具有功能强.使用简便.管理方便.运行速度快.安全可靠性强等优点,用户可利用许多语言编写访问m ...

  10. 处理字符串中的换行,将textarea中的带有换行的字符串变为逗号分隔的写法

    _setMultipleInputValues: function (param) { //Maybe need to modify here for the new parameter //add ...