摘要:说到代码混淆艺术,大家可能会想到 IOCCC 大赛中的阿卡林作品,不过本文介绍 Andy Sloane 的一个作品:C 代码实现雅虎 logo ACSII 动图。它是一个 20fps、抗锯齿的 Yahoo! logo ASCII 动画。

说到代码混淆艺术,大家可能会想到 IOCCC 大赛中的阿卡林作品(如下图)。

462 字节 C 代码实现雅虎 logo ACSII 动画-品味雅虎

“阿卡林”,动漫作品《摇曳百合》中的主人公之一

不过本文介绍 Andy Sloane 的一个作品:C 代码实现雅虎 logo ACSII 动图。自从他把这段代码放到我们的内网以来,一直有做优化改进,下面是最终版。来吧,试一下:

Shell

1

2

3

4

5

6

7

8

9

10

11

$cat>yanim.c

c,p,i,j,n,F=40,k,m;floata,x,y,S=0,V=0;main(){for(;F--;usleep(50000),F?puts(

"x1b[25A"):0)for(S+=V+=(1-S)/10-V/4,j=0;j<72;j+=3,putchar(10))for(i=0;x=S*(

i-27),i++<73;putchar(c[" ''".$u$"]))for(c=0,n=3;n--;)for(y=S*(j+n-36),k=0,c

^=(136*x*x+84*y*y<92033)<<n,p=6,m=0;m<8;k++["<[]O=IKNAL;KNRbF8EbGEROQ@BSX"

"XtG!#t3!^"]/1.16-68>x*cos(a)+y*sin(a)?k=p,p="<AFJPTX"[m++]-50:k==p?c^=1<<n,

m=8:0)a=(k["O:85!fI,wfO8!yZfO8!f*hXK3&fO;:O;#hP;"i[by asloane"]-79)/14.64;}

^D

$gcc-oyanim yanim.c-lm

[warnings which real programmers ignore]

$./yanim

译注:把上述代码改一下,让大家可以看得清晰一点。编译的方法是一样的哦。

C

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

#include <stdio.h>

#include <math.h>

#include <unistd.h>

intc,p,i,j,n,F=40,k,m;

floata,x,y,S=0,V=0;

voidmain()

{

for(;F--;usleep(50000),F?puts("x1b[25A"):0)

{

for(S+=V+=(1-S)/10-V/4,j=0;j<72;j+=3,putchar(10))

{

for(i=0;x=S*(i-27),i++<73;putchar(c[" ''".$u$"]))

{

for(c=0,n=3;n--;)

{

for(y=S*(j+n-36),k=0,c^=(136*x*x+84*y*y<92033)<<n,p=6,m=0;m<8;

k++["<[]O=IKNAL;KNRbF8EbGEROQ@BSX""XtG!#t3!^"]/1.16-68>x*cos(a)+y*sin(a)?k=p,p="<AFJPTX"[m++]-50:k==p?c^=1<<n,m=8:0)

{

a=(k["O:85!fI,wfO8!yZfO8!f*hXK3&fO;:O;#hP;"i[by asloane"]-79)/14.64;

}

}

}

}

}

return;

}

运行后,你将会看到:

462 字节 C 代码实现雅虎 logo ACSII 动画-品味雅虎

(GIF 动图,伯小乐转制)

它是一个 20fps、抗锯齿的 Yahoo! logo ASCII 动画。如果你想自己弄清楚它是如何工作的,那么可以忽略下面的内容。否则,请往下读。

我鼓励大家研究一下代码中的常量:

S+=V+=(1-S)/10-V/4是该动画的欠阻尼控制系统——S 是缩放(=1/缩放倍数)、V 是速度、1/10 和 1/4 是 PD 常量。在第一帧中 S=0 相当于无穷放大。S<0 是非法的。F 是帧数计数器。1.16 控制多边形绘制的缩放比例(68 是 79/1.16 的近似值,所以也需要对它进行校正),而 136、84、92033 定义了一个椭圆。14.64 是一个不可调参数(它是 46/π 的值)。

抗锯齿的方法很简单:

每个字符由三组垂直排列的样本和一个 8 字符的查找表(用于为三组打开或者关闭的排列中的每组提供像素)组成。每一帧包含 73×24 个字符或者 73×72 个像素。水平方向使用 73 列这个取值有些随意;我预测可以增加到 79(译注:一般终端一行可以容纳 80 个像素,去掉一个换行符就剩下 79)。

通过一种(我认为)相当简洁的方法,亚像素精度和无帧缓冲,将 logo 绘制成一个椭圆和八个凸多边形。它需要一些设计上的权衡来生成两个可打印的字符数组,但是这比起绘制三角形到帧缓冲所需的代码要更少,这也是多边形栅格化的典型做法。

为了制作上述的图案,首先需要把 logo “Y!”向量化。我通过测量基准图和在坐标纸上画出坐标实现 logo “Y!”的向量化。然后,我写了一个应用程序,将点和多边形定义转换成如下定义的角度和偏移量(直到我从代码中找到一些亮点可以放到我的博客中之前,我会一直把这段代码放到 pastebin 上)。

这里的椭圆是高中数学中的标准椭圆:x2/a2+ y2/b2< 1。检查每个点,如果该点在椭圆内,那么绘制该像素(136x2+ 84y2< 92033 是对 a和 b进行简单移项的结果,其中 a和 b分别是从原图片测量出来、缩放到像素网格的椭圆的两个轴半径)。

每个多边形由一系列独立的半平面组成(一个半平面即一条无限延长线的一边中所有点的集合)。如果一个点在所有半平面“里面”,那么它就处于该多边形里面(这仅仅适用于凸多边形),而该像素用异或操作符 ^ 进行切换(因此不需要任何特殊的操作,就处理了椭圆里面的“反面”部分和“正面”的感叹号)。多边形的每条边都被定义为方程式 ax+ by> c。我使用角度 θ来表示 a和 b,即 a= cos(θ) 和 b= sin(θ),并以 π/46 的增量来量化角度——得到用 ASCII 码的 33 到 125(即 ‘!’ 到 ‘}’)来表示角度 -π 到 +π,其中角度 0 表示为 ‘O’(即 ASCII 码的 79)。然后对 c进行求解,同样以缩放增量对 -47 到 47 进行量化,而这条边的中点也被认为在多边形内。

下面是粗略的示意图(这是我在纸上画的,我的制图软件都用不了。大家将就一下):

462 字节 C 代码实现雅虎 logo ACSII 动画-品味雅虎

阴影部分是 ax+ by< c,代表在多边形外面,而虚线则是 ax+ by= c。

(a,b) 构成线段的正交向量。它们指向多边形的内部,所以我们可以直接从定义线段的点得到 a和 b,即将定义一条边的向量 (x1– x0, y1– y0) 旋转 90 度,得到 (a, b) = (y1– y0, x0– x1)。然后,由于实际的大小不重要,所以我们对 (a,b) 进行归一化。当我们求解角度时,它们会变成 1。我们在后面可以抵消 c的取值(假设 ax+by>c,那么对于缩放倍数 s>0,可以得到 sax+sby>sc)。接着计算 θ= atan2(a,b),将其量化成我们 94 个角度中的一个,并得到 (a,b) = (cos(θ), sin(θ))。

通过直接将多边形一条边上的任意一个点代入公式 c= ax+by,可以很容易地得到 c。我使用边上线段的中点,即 (xt, yt) = ((x0+ x1)/2, (y0+ y1)/2),因为在我们量化 θ之后,边的角度会出现轻微的偏移,而这会导致超出边长的错误。

注意到开头的几帧(你可以按 ^S 来停止,^Q 来继续——xon/xoff),由于在分离半平面方程式时出现的量化错误,导致“Y”的底部被咬掉几口。(译注:^S 即键盘的 Ctrl+S,^Q 即键盘的 Ctrl+Q)

通过仔细地重排分离平面的数组,使得大部分绘图区被排除,可以稍微提高 CPU 的效率。我没有把这部分放到 generator 的代码中。

动画是由序列 <ESC>[25A 完成——在任意终端仿真模式下,将光标上移 25 行。严格的说我只需上移 24 行,但是 puts比 printf简洁,而它会隐式地添加新行。如果你的终端少于 26 行,那么它会对你的终端回滚做一些奇怪的事情。另外,usleep用来把动画的刷新率限制到 20fps,这也是唯一一个非 ANSIC 字符的地方。

然后通过使用精妙的 for循环和 C 语言默认包含的不正规的逗号、条件语句和全局 int变量的优点(这在 C 语言混乱代码中是比较常见的)。而那几乎展现出这段代码是如何实现的所有秘密。

使用一组不同的移动序列或者旋转(再或者任意的 3D 切换,这基本上是追踪 logo 的轨迹),可以很简单地改进这段动画。我仅仅用了缩放动画来证明一点:这个 logo 是被动态地绘制,而不仅仅是一个压缩后的 logo。并且我会使动画保持简短而流畅。

我提前为文中各种各样的错误向大家道歉,也难免会在写这篇文章的时候出错,但是你了解到了这个想法。

本文由 伯乐在线- Sam Lin翻译

历史上的今天:

  1. 2013:  雅虎帐号注册界面和找回密码方式发生改变(6)
  2. 2013:  雅虎流量超谷歌 它真的就赢了吗?(0)
  3. 2012:  忘掉 iPhone 和免费食物吧,告诉你梅耶尔带给雅虎的真正改变(0)
  4. 2011:  新版雅虎邮箱月活跃用户人数突破一亿(0)
  5. 2010:  雅虎高管称将专注内容,看好本地搜索发展(0)