全彩 ASCII 艺术曾经在 DOS 上非常流行,DOS 可以利用扩展的 ASCII 字符集及其绘图元素集合。您可以通过添加 ASCII 艺术作为炫酷的“欢迎”屏幕或带有更多程序信息的彩色“退出”屏幕,为您的下一个 FreeDOS 程序增加一些视觉趣味。
但是这种风格的 ASCII 艺术并不局限于 FreeDOS 应用程序。您可以在 Linux 终端模式程序中使用相同的方法。虽然 Linux 使用 ncurses 来控制屏幕,而不是 DOS 的 conio,但相关概念很好地适用于 Linux 程序。本文着眼于如何从 C 程序生成彩色 ASCII 艺术。
ASCII 艺术文件
您可以使用各种工具来绘制您的 ASCII 艺术。对于此示例,我使用了名为 TheDraw 的旧 DOS 应用程序,但您可以在 Linux 上找到现代开源 ASCII 艺术程序,例如 Moebius (Apache 许可证) 或 PabloDraw (MIT 许可证)。您使用什么工具并不重要,只要您知道保存的数据是什么样子即可。
这是一个示例 ASCII 艺术文件的一部分,保存为 C 源代码。请注意,代码片段定义了一些值:IMAGEDATA_WIDTH
和 IMAGEDATA_DEPTH
定义了屏幕上的列数和行数。在本例中,它是一个 80x25 ASCII 艺术“图像”。IMAGEDATA_LENGTH
定义了 IMAGEDATA
数组中的条目数。ASCII 艺术屏幕中的每个字符都可以用两个字节的数据表示:要显示的字符和一个颜色属性,其中包含字符的前景色和背景色。对于 80x25 的屏幕,其中每个字符都与一个属性配对,该数组包含 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,
等等,其余的数组也是如此。
要将此 ASCII 艺术显示到屏幕上,您需要编写一个小程序来读取数组并使用正确的颜色打印每个字符。
设置颜色属性
此 ASCII 艺术文件中的颜色属性在一个字节中定义了背景色和前景色,用十六进制值表示,例如 0x08
或 0x6E
。事实证明,十六进制是表达这种颜色“对”的紧凑方式。
字符模式系统,例如 Linux 上的 ncurses 或 DOS 上的 conio 只能显示十六种颜色。那是十六种可能的文本颜色和八种背景颜色。在二进制中计数十六个值(从 0 到 15)只需要四位
1111
在二进制中是 16
方便的是,十六进制可以使用单个字符表示 0 到 15:0、1、2、3、4、5、6、7、8、9、A、B、C、D、E 和 F。因此,十六进制值 F
是数字 15,或二进制中的 1111
。
使用颜色对,您可以在一个八位字节中编码背景色和前景色。文本颜色为四位(十六进制为 0 到 15 或 0 到 F),背景色为三位(十六进制为 0 到 7 或 0 到 E)。字节中剩余的位在此处未使用,因此我们可以忽略它。
要将颜色对或属性转换为程序可以使用的颜色值,您需要使用位掩码来仅指定用于文本颜色或背景色的位。在 FreeDOS 上使用 OpenWatcom C 编译器,您可以编写此函数以从颜色属性适当设置颜色
void
textattr(int newattr)
{
_settextcolor(newattr & 15); /* 0000xxxx */
_setbkcolor((newattr >> 4) & 7); /* 0xxx0000 */
}
_settextcolor
函数仅设置文本颜色,_setbkcolor
函数设置背景颜色。两者都在 graph.h
中定义。请注意,由于颜色属性在一个字节值中同时包含背景色和前景色,因此 textattr
函数使用 &
(二进制 AND)来设置位掩码,该位掩码仅隔离属性中的最后四位。这就是颜色对存储前景色值 0 到 15 的位置。
要获取背景色,该函数首先执行位移,以将位“推”到右侧。这会将“高”位放入“低”位范围,因此任何类似 0xxx0000
的位都会变为 00000xxx
。我们可以使用另一个带有 7(二进制 0111
)的位掩码来选出背景色值。
显示 ASCII 艺术
IMAGEDATA
数组包含整个 ASCII 艺术屏幕以及每个字符的颜色值。要将 ASCII 艺术显示到屏幕上,您的程序需要扫描数组,设置颜色属性,然后一次显示一个字符的屏幕。
让我们在屏幕底部留出空间,以便为用户显示单独的消息或提示。这意味着我不希望显示 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
循环内部,我们需要设置颜色,然后打印字符。OpenWatcom C 编译器提供了一个函数 _outtext
,用于使用当前颜色值显示文本。但是,这需要传递一个字符串,并且如果我们需要一次处理每个字符,以防一行上的每个字符都需要不同的颜色,则效率会很低。
相反,OpenWatcom 有一个类似的函数 _outmem
,允许您指示要显示多少个字符。对于一次一个字符,我们可以提供一个指向 IMAGEDATA
数组中字符值的指针,并告诉 _outtext
仅显示一个字符。这将使用当前颜色属性显示字符,这正是我们需要的。
for (pos = 0; pos < 3840; pos += 2) {
ch = &IMAGEDATA[pos]; /* pointer assignment */
attr = IMAGEDATA[pos + 1];
textattr(attr);
_outmem(ch, 1);
}
这个更新后的 for
循环通过分配一个指向 IMAGEDATA
数组中的指针来设置字符 ch
。接下来,循环设置文本属性,然后使用 _outmem
显示字符。
将它们放在一起
借助 textattr
函数和用于处理数组的 for
循环,我们可以编写一个完整的程序来显示 ASCII 艺术文件的内容。对于此示例,将 ASCII 艺术另存为 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 编译器编译程序,您将获得一个新程序,该程序显示此节日消息

万圣节快乐 (CC-BY-SA 4.0)
祝大家万圣节快乐!
2 条评论