在 Linux 上用 ASCII 藝術(shù)打印萬圣節(jié)問候語
使用 Linux 或 FreeDOS 從一個 C 程序中生成彩色的 ASCII 藝術(shù)。
利用擴展 ASCII 字符集和它的繪畫元素集合的全彩 ASCII 藝術(shù)在 DOS 上曾經(jīng)相當(dāng)流行。你可以在你的下一個 FreeDOS 程序中加入 ASCII 藝術(shù),作為一個很酷的“歡迎”屏幕,或者作為一個提供了更多程序信息的彩色“退出”屏幕,來增加一點視覺上的樂趣。
但是,這種 ASCII 藝術(shù)的風(fēng)格并不僅僅局限于 FreeDOS 程序。你可以在 Linux 終端模式的程序中使用同樣的方法。雖然 Linux 使用 ncurses 來控制屏幕,而不是 DOS 的 conio,但相關(guān)的概念也適用于 Linux 程序。本文探討了如何從 C 語言程序中生成彩色 ASCII 藝術(shù)。
ASCII 藝術(shù)文件
你可以使用各種工具來繪制你的 ASCII 藝術(shù)。在這個例子中,我使用了一個叫做 TheDraw 的老式 DOS 應(yīng)用程序,但是你可以在 Linux 上找到現(xiàn)代的開源 ASCII 藝術(shù)程序,比如 Moebius(Apache 許可證)或者 PabloDraw(MIT 許可證)。只要你知道保存的數(shù)據(jù)是什么樣子的,你使用什么工具并不重要。
下面是一個 ASCII 藝術(shù)文件樣本的一部分,以 C 源代碼保存。請注意,這個代碼片段定義了幾個值。IMAGEDATA_WIDTH
和 IMAGEDATA_DEPTH
定義了屏幕上的列數(shù)和行數(shù)。在這里,它是一個 80x25 的 ASCII 藝術(shù)“圖像”。IMAGEDATA_LENGTH
定義了 IMAGEDATA
數(shù)組中的條目數(shù)量。ASCII 藝術(shù)畫面中的每個字符可以用兩個字節(jié)的數(shù)據(jù)表示。要顯示的字符和包含該字符的前景和背景顏色的顏色屬性。對于一個 80x25 的屏幕,每個字符都與一個屬性配對,該數(shù)組包含 4000 個條目(即 80*25*2=4000
)。
#define IMAGEDATA_WIDTH 80
#define IMAGEDATA_DEPTH 25
#define IMAGEDATA_LENGTH 4000
unsigned char IMAGEDATA [] = {
'.', 0x08, ' ', 0x08, ' ', 0x08, ' ', 0x08, ' ', 0x08, ' ', 0x08,
' ', 0x08, ' ', 0x08, '.', 0x0F, ' ', 0x08, ' ', 0x08, ' ', 0x08,
' ', 0x08, ' ', 0x08, ' ', 0x08, ' ', 0x08, ' ', 0x08, '.', 0x0F,
' ', 0x08, ' ', 0x08, ' ', 0x08, ' ', 0x08, ' ', 0x08, ' ', 0x08,
' ', 0x08, ' ', 0x08, ' ', 0x08, ' ', 0x08, ' ', 0x08, ' ', 0x08,
數(shù)組的其它部分依此類推。
為了在屏幕上顯示這種 ASCII 藝術(shù),你需要寫一個小小的程序來讀取數(shù)組并以正確的顏色打印每個字符。
設(shè)置一個顏色屬性
這個 ASCII 藝術(shù)文件中的顏色屬性在一個字節(jié)中定義了背景和前景的顏色,用十六進(jìn)制的值表示,如 0x08
或 0x6E
。十六進(jìn)制是適合表達(dá)這樣的顏色“對”的緊湊方式。
像 Linux 上的 ncurses 或 DOS 上的 conio 這樣的字符模式系統(tǒng) 只能顯示 16 種顏色。這就是十六種可能的文本顏色和八種背景顏色。用二進(jìn)制計算十六個值(從 0 到 15)只需要四個二進(jìn)制位。
1111
是二進(jìn)制的 15
而且方便的是,十六進(jìn)制可以用一個字符表示 0 到 15:0
、1
、2
、3
、4
、5
、6
、7
、8
、9
、A
、B
、C
、D
、E
和 F
。所以十六進(jìn)制的值 F
是數(shù)字 15,或二進(jìn)制的 1111
。
通過顏色對,你可以用一個八位的字節(jié)來編碼背景和前景的顏色。這就是文本顏色的四個二進(jìn)制位(十六進(jìn)制中的 0 到 15 或 0 到 F)和背景顏色的三個二進(jìn)制位(十六進(jìn)制中的 0 到 7 或 0 到 E)。字節(jié)中剩余的二進(jìn)制位在這里沒有使用,所以我們可以忽略它。
為了將顏色對或?qū)傩赞D(zhuǎn)換成你的程序可以使用的顏色值,你需要 使用位掩碼,只指定用于文字顏色或背景顏色的位。使用 FreeDOS 上的 OpenWatcom C 編譯器,你可以編寫這個函數(shù),從顏色屬性中適當(dāng)?shù)卦O(shè)置顏色。
void
textattr(int newattr)
{
_settextcolor(newattr & 15); /* 0000xxxx */
_setbkcolor((newattr >> 4) & 7); /* 0xxx0000 */
}
_settextcolor
函數(shù)只設(shè)置文本顏色,_setbkcolor
函數(shù)設(shè)置背景顏色。兩者都定義在 graph.h
中。注意,由于顏色屬性在一個字節(jié)值中包括了背景色和前景色,textattr
函數(shù)使用 &
(二進(jìn)制的“與”運算)來設(shè)置一個位掩碼,只隔離了屬性中的最后四個位。這就是顏色對存儲前景顏色的值 0 到 15 的地方。
為了得到背景色,該函數(shù)首先執(zhí)行了一個位移,將位“推”到右邊。這就把“上”位放到了“下”位范圍,所以任何像 0xxx0000
這樣的位都變成了 00000xxx
。我們可以用另一個的位掩碼 7(二進(jìn)制 0111
)來挑選出背景顏色值。
顯示 ASCII 藝術(shù)
IMAGEDATA
數(shù)組包含整個 ASCII 藝術(shù)屏幕和每個字符的顏色值。為了在屏幕上顯示 ASCII 藝術(shù),你的程序需要掃描該數(shù)組,設(shè)置顏色屬性,然后一次在屏幕上顯示一個字符。
讓我們在屏幕的底部留出空間,以便向用戶提供單獨的信息或提示。也就是說,我不想顯示一個 80 列 ASCII 屏幕的所有 25 行,而只想顯示前 24 行。
/* print one line less than the 80x25 that's in there:
80 x 24 x 2 = 3840 */
for (pos = 0; pos < 3840; pos += 2) {
...
}
在 for
循環(huán)里面,我們需要設(shè)置顏色,然后打印字符。OpenWatcom C 編譯器提供了一個函數(shù) _outtext
來顯示帶有當(dāng)前顏色值的文本。然而,這需要傳遞一個字符串,如果我們需要一個一個地處理每個字符,在一行中的每個字符需要不同顏色的情況下,效率就會很低。
相反,OpenWatcom 有一個類似的函數(shù),叫做 _outmem
,允許你指示要顯示多少個字符。對于一次一個字符,我們可以在 IMAGEDATA
數(shù)組中提供一個字符值的指針,并告訴 _outtext
只顯示一個字符。這將使用當(dāng)前的顏色屬性顯示該字符,這就是我們需要的。
for (pos = 0; pos < 3840; pos += 2) {
ch = &IMAGEDATA[pos]; /* pointer assignment */
attr = IMAGEDATA[pos + 1];
textattr(attr);
_outmem(ch, 1);
}
這個更新的 for
循環(huán)通過向 IMAGEDATA
數(shù)組分配一個指針來設(shè)置字符 ch
。接下來, 循環(huán)設(shè)置文本屬性, 然后用 _outmem
顯示字符.
整合起來
有了 textattr
函數(shù)和處理數(shù)組的 for
循環(huán), 我們可以編寫一個完整的程序來顯示 ASCII 藝術(shù)文件的內(nèi)容。對于這個例子,將 ASCII 藝術(shù)文件保存為 imgdata.inc
,并用 #include
語句將其包含在源文件中。
#include <stdio.h>
#include <conio.h>
#include <graph.h>
#include "imgdata.inc"
void
textattr(int newattr)
{
_settextcolor(newattr & 15); /* 0000xxxx */
_setbkcolor((newattr >> 4) & 7); /* 0xxx0000 */
}
int
main()
{
char *ch;
int attr;
int pos;
if (_setvideomode(_TEXTC80) == 0) {
fputs("Error setting video mode", stderr);
return 1;
}
/* draw the array */
_settextposition(1, 1); /* top left */
/* print one line less than the 80x25 that's in there:
80 x 24 x 2 = 3840 */
for (pos = 0; pos < 3840; pos += 2) {
ch = &IMAGEDATA[pos]; /* pointer assignment */
attr = IMAGEDATA[pos + 1];
textattr(attr);
_outmem(ch, 1);
}
/* done */
_settextposition(25, 1); /* bottom left */
textattr(0x0f);
_outtext("Press any key to quit");
getch();
textattr(0x00);
return 0;
}
在 FreeDOS 上使用 OpenWatcom C 編譯器編譯該程序,你會得到一個顯示這個節(jié)日信息的新程序。
萬圣節(jié)快樂(Jim Hall, CC-BY-SA 4.0)
萬圣節(jié)快樂,各位!