462 位元組 C 程式碼實現雅虎 logo ACSII 動畫

Sam Lin發表於2016-08-21

說到程式碼混淆藝術,大家可能會想到 IOCCC 大賽中的阿卡林作品(如下圖)。

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

不過本文介紹 Andy Sloane 的一個作品:C 程式碼實現雅虎 logo ACSII 動圖。自從他把這段程式碼放到我們的內網以來,一直有做優化改進,下面是最終版。來吧,試一下:

462 位元組 C 程式碼實現雅虎 logo ACSII 動畫

[2016-08-23 編輯更新]:因站內第三方外掛之間相容性問題,導致程式碼段幾個特殊字元被刪,故而改成程式碼截圖了。原始碼請看英文

譯註:把上述程式碼改一下,讓大家可以看得清晰一點。編譯的方法是一樣的哦。

462 位元組 C 程式碼實現雅虎 logo ACSII 動畫

執行後,你將會看到:

animation of the Yahoo! logo

(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 是對 ab 進行簡單移項的結果,其中 ab 分別是從原圖片測量出來、縮放到畫素網格的橢圓的兩個軸半徑)。

每個多邊形由一系列獨立的半平面組成(一個半平面即一條無限延長線的一邊中所有點的集合)。如果一個點在所有半平面“裡面”,那麼它就處於該多邊形裡面(這僅僅適用於凸多邊形),而該畫素用異或操作符 ^ 進行切換(因此不需要任何特殊的操作,就處理了橢圓裡面的“反面”部分和“正面”的感嘆號)。多邊形的每條邊都被定義為方程式 ax + by > c。我使用角度 θ 來表示 ab,即 a = cos(θ) 和 b = sin(θ),並以 π/46 的增量來量化角度——得到用 ASCII 碼的 33 到 125(即 ‘!’ 到 ‘}’)來表示角度 -π 到 +π,其中角度 0 表示為 ‘O’(即 ASCII 碼的 79)。然後對 c 進行求解,同樣以縮放增量對 -47 到 47 進行量化,而這條邊的中點也被認為在多邊形內。

下面是粗略的示意圖(這是我在紙上畫的,我的製圖軟體都用不了。大家將就一下):

polygon_separation

陰影部分是 ax + by < c,代表在多邊形外面,而虛線則是 ax + by = c

(a,b) 構成線段的正交向量。它們指向多邊形的內部,所以我們可以直接從定義線段的點得到 ab,即將定義一條邊的向量 (x1x0, y1y0) 旋轉 90 度,得到 (a, b) = (y1y0, x0x1)。然後,由於實際的大小不重要,所以我們對 (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 行,但是 putsprintf 簡潔,而它會隱式地新增新行。如果你的終端少於 26 行,那麼它會對你的終端回滾做一些奇怪的事情。另外,usleep 用來把動畫的重新整理率限制到 20fps,這也是唯一一個非 ANSIC 字元的地方。

然後通過使用精妙的 for 迴圈和 C 語言預設包含的不正規的逗號、條件語句和全域性 int 變數的優點(這在 C 語言混亂程式碼中是比較常見的)。而那幾乎展現出這段程式碼是如何實現的所有祕密。

使用一組不同的移動序列或者旋轉(再或者任意的 3D 切換,這基本上是追蹤 logo 的軌跡),可以很簡單地改進這段動畫。我僅僅用了縮放動畫來證明一點:這個 logo 是被動態地繪製,而不僅僅是一個壓縮後的 logo。並且我會使動畫保持簡短而流暢。

我提前為文中各種各樣的錯誤向大家道歉,也難免會在寫這篇文章的時候出錯,但是你瞭解到了這個想法。

打賞支援我翻譯更多好文章,謝謝!

打賞譯者

打賞支援我翻譯更多好文章,謝謝!

462 位元組 C 程式碼實現雅虎 logo ACSII 動畫

相關文章