用盲文字符来在终端画黑白图像

用盲文字符来在终端画黑白图像

食用提示

如果这篇文章在您的设备上显示很多方框,或许是字体出了问题。请确保自己使用的字体可以正常显示盲文。

在我的设备上,无论怎么操作都无法使 urxvt (rxvt , xterm ) 表现出我想要的样子。因此在不建议您进行实验时使用 urxvt (rxvt , xterm ) 。

想法来源

有一天发现盲文就是一堆像素点,就想着用盲文文字在终端画图。

实现效果

show1.png
show2.png

分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
⠀⠁⠂⠃⠄⠅⠆⠇⠈⠉⠊⠋⠌⠍⠎⠏
⠐⠑⠒⠓⠔⠕⠖⠗⠘⠙⠚⠛⠜⠝⠞⠟
⠠⠡⠢⠣⠤⠥⠦⠧⠨⠩⠪⠫⠬⠭⠮⠯
⠰⠱⠲⠳⠴⠵⠶⠷⠸⠹⠺⠻⠼⠽⠾⠿
⡀⡁⡂⡃⡄⡅⡆⡇⡈⡉⡊⡋⡌⡍⡎⡏
⡐⡑⡒⡓⡔⡕⡖⡗⡘⡙⡚⡛⡜⡝⡞⡟
⡠⡡⡢⡣⡤⡥⡦⡧⡨⡩⡪⡫⡬⡭⡮⡯
⡰⡱⡲⡳⡴⡵⡶⡷⡸⡹⡺⡻⡼⡽⡾⡿
⢀⢁⢂⢃⢄⢅⢆⢇⢈⢉⢊⢋⢌⢍⢎⢏
⢐⢑⢒⢓⢔⢕⢖⢗⢘⢙⢚⢛⢜⢝⢞⢟
⢠⢡⢢⢣⢤⢥⢦⢧⢨⢩⢪⢫⢬⢭⢮⢯
⢰⢱⢲⢳⢴⢵⢶⢷⢸⢹⢺⢻⢼⢽⢾⢿
⣀⣁⣂⣃⣄⣅⣆⣇⣈⣉⣊⣋⣌⣍⣎⣏
⣐⣑⣒⣓⣔⣕⣖⣗⣘⣙⣚⣛⣜⣝⣞⣟
⣠⣡⣢⣣⣤⣥⣦⣧⣨⣩⣪⣫⣬⣭⣮⣯
⣰⣱⣲⣳⣴⣵⣶⣷⣸⣹⣺⣻⣼⣽⣾⣿

这是 UTF-8 中的盲文字符。共有 256 个。每个盲文字符都由数个点组成。点最多的盲文字符(右下角)有 8 个点,它看起来像个实心黑框框;点最少的盲文字符有 0 个点(左上角),虽然它看上去像个空格,但它真的和空格不是一个东西。

稍微观察可以发现,一个盲文字符可以当成 4x2 的小形位图使用,如果能够良好组织,使盲文字符按某种方式排列,就可以拼出大一些的位图。

不同的 4x2 位图共有 2^8 = 256 个,而不同的盲文字符正好也有 256 个。这意味着盲文字符和 4x2 的位图之间有着一一对应的关系。为了方便盲文与位图的与相转化,我们需要设计一种编码方案。

上面列出的表显然是经过良好组织的,可以发现盲文字符的排布很有规律。找规律的过程略去不提,这里仅说编码方案。经过以下操作后,可以保证盲文字符和其对应的 4x2 位图有相同的编号:

盲文:将上表中的盲文从上到下,从左到右依次编号 0 到 255 。

位图:考虑搞一张权值表:

1
2
3
4
1  8
2 16
4 32
64 128

将表中所有对应位图黑色位置的权值加起来,得到的和即为位图的编号。

例如,字符 “⢫” ,其编号为 171 ,其位图为:

1
2
3
4
1 1
1 0
0 1
0 1

和权值表 py 后得到 1 + 8 + 2 + 32 + 128 = 171 ,和期待结果一致。

使用这个方法,可以将 4x2 的小位图和它所对应的盲文字符的编号对应起来。于是,我们就可以在终端画图了。

实现

首先对盲文字符打表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const char *magic_table[] = {
"⠀", "⠁", "⠂", "⠃", "⠄", "⠅", "⠆", "⠇", "⠈", "⠉", "⠊", "⠋", "⠌", "⠍", "⠎", "⠏",
"⠐", "⠑", "⠒", "⠓", "⠔", "⠕", "⠖", "⠗", "⠘", "⠙", "⠚", "⠛", "⠜", "⠝", "⠞", "⠟",
"⠠", "⠡", "⠢", "⠣", "⠤", "⠥", "⠦", "⠧", "⠨", "⠩", "⠪", "⠫", "⠬", "⠭", "⠮", "⠯",
"⠰", "⠱", "⠲", "⠳", "⠴", "⠵", "⠶", "⠷", "⠸", "⠹", "⠺", "⠻", "⠼", "⠽", "⠾", "⠿",

"⡀", "⡁", "⡂", "⡃", "⡄", "⡅", "⡆", "⡇", "⡈", "⡉", "⡊", "⡋", "⡌", "⡍", "⡎", "⡏",
"⡐", "⡑", "⡒", "⡓", "⡔", "⡕", "⡖", "⡗", "⡘", "⡙", "⡚", "⡛", "⡜", "⡝", "⡞", "⡟",
"⡠", "⡡", "⡢", "⡣", "⡤", "⡥", "⡦", "⡧", "⡨", "⡩", "⡪", "⡫", "⡬", "⡭", "⡮", "⡯",
"⡰", "⡱", "⡲", "⡳", "⡴", "⡵", "⡶", "⡷", "⡸", "⡹", "⡺", "⡻", "⡼", "⡽", "⡾", "⡿",

"⢀", "⢁", "⢂", "⢃", "⢄", "⢅", "⢆", "⢇", "⢈", "⢉", "⢊", "⢋", "⢌", "⢍", "⢎", "⢏",
"⢐", "⢑", "⢒", "⢓", "⢔", "⢕", "⢖", "⢗", "⢘", "⢙", "⢚", "⢛", "⢜", "⢝", "⢞", "⢟",
"⢠", "⢡", "⢢", "⢣", "⢤", "⢥", "⢦", "⢧", "⢨", "⢩", "⢪", "⢫", "⢬", "⢭", "⢮", "⢯",
"⢰", "⢱", "⢲", "⢳", "⢴", "⢵", "⢶", "⢷", "⢸", "⢹", "⢺", "⢻", "⢼", "⢽", "⢾", "⢿",

"⣀", "⣁", "⣂", "⣃", "⣄", "⣅", "⣆", "⣇", "⣈", "⣉", "⣊", "⣋", "⣌", "⣍", "⣎", "⣏",
"⣐", "⣑", "⣒", "⣓", "⣔", "⣕", "⣖", "⣗", "⣘", "⣙", "⣚", "⣛", "⣜", "⣝", "⣞", "⣟",
"⣠", "⣡", "⣢", "⣣", "⣤", "⣥", "⣦", "⣧", "⣨", "⣩", "⣪", "⣫", "⣬", "⣭", "⣮", "⣯",
"⣰", "⣱", "⣲", "⣳", "⣴", "⣵", "⣶", "⣷", "⣸", "⣹", "⣺", "⣻", "⣼", "⣽", "⣾", "⣿"
};

接着实现 canvas 结构体。这里用 unsigned char 数组当成 bool 数组使用。日后优化时,可以用 bitmap 节省空间。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
typedef struct canvas {
int width;
int height;
void *buf;
} canvas;

int canvas_init(canvas *p, int width, int height)
{
width = ((width - 1) / 2 + 1) * 2;
height = ((height - 1) / 4 + 1) * 4;
p->width = width;
p->height = height;
p->buf = malloc(sizeof(unsigned char) * width * height);
if (p->buf == NULL)
return 1;
return 0;
}

void canvas_clear(canvas p)
{
free(p.buf);
}

实现画像素点和打印功能:

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
29
30
31
32
33
void canvas_draw(canvas p, int x, int y)
{
((unsigned char (*)[p.width])p.buf)[y][x] = 1;
}

void canvas_erase(canvas p, int x, int y)
{
((unsigned char (*)[p.width])p.buf)[y][x] = 0;
}

int canvas_test(canvas p, int x, int y)
{
return ((unsigned char (*)[p.width])p.buf)[y][x];
}

void canvas_print(canvas p)
{
int i, j, k, l;
for (i = p.height; i > 0; i -= 4) {
for (j = 0; j < p.width; j += 2) {
int id = 0;
for (l = 1; l >= 0; --l)
for (k = 3; k >= 1; --k)
id = (id << 1) | canvas_test(p, j + l, i - k);
if (canvas_test(p, j, i - 4))
id += 64;
if (canvas_test(p, j + 1, i - 4))
id += 128;
printf("%s", magic_table[id]);
}
putchar('\n');
}
}

实现完成。以下是函数功能与参数说明:

1
2
3
4
5
6
int canvas_init(canvas *p, int width, int height); 将 p 初始化为宽 width 高 height 的画布
void canvas_clear(canvas p); 销毁画布 p
void canvas_draw(canvas p, int x, int y); 在 p 的 (x, y) 位置画上一个像素点
void canvas_erase(canvas p, int x, int y); 擦除 p 中 (x, y) 位置上的像素点
int canvas_test(canvas p, int x, int y); 返回 p 中 (x, y) 上是否已经画过
void canvas_print(canvas p); 打印 p

实现示例中的效果

用 ImageMagick 的 convert 命令将图片文件转为只有 2 种颜色的 xpm 文件,写个傻瓜 xpm 解析器,配合上面的代码简单处理即可得到示例中的效果。傻瓜解析器的代码见:doxpm.c

在本机上,实现示例效果的命令为:

1
2
3
4
5
$ convert -colors 2 sample.png a.xpm
$ gcc doxpm.c -o doxpm
$ ./doxpm
$ # 如果需要彩色的话:
$ ./doxpm | lolcat

代码仅被用来说明想法,并没有想写成一个可用的库。所以码风略快糙猛请多包涵。

感谢 zrz_orz 同学教我在洛谷日报上投稿,并提出大量修改意见。

用盲文字符来在终端画黑白图像

https://jyi2ya.github.io/2023/10/01/tutorial/canvas/main/

作者

jyi2ya

发布于

2023-10-01

更新于

2024-02-25

许可协议

You need to set client_id and slot_id to show this AD unit. Please set it in _config.yml.