Use Braille characters to draw black-and-white images on the terminal

Posted by Eal on Tue, 25 Jan 2022 20:31:22 +0100

Suddenly I found a relevant library... I'm building wheels

Use Braille characters to draw black-and-white images on the terminal

Edible tips

If this article displays many boxes on your device, there may be a problem with the font. Please make sure that your font can display Braille normally.

On my device, no matter how I operate it, I can't make urxvt (rxvt, xterm) show what I want. Therefore, it is not recommended that you use urxvt (rxvt, xterm) when experimenting.

Source of ideas

One day I found that braille is a pile of pixels, so I wanted to draw pictures on the terminal with Braille.

Realization effect


analysis

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

This is a Braille character in UTF-8. There are 256 in total. Each Braille character consists of several dots. The Braille character with the most dots (lower right corner) has 8 dots, which looks like a solid black frame; The Braille character with the least dots has 0 dots (upper left corner). Although it looks like a space, it is really not the same thing as a space.

A little observation shows that a Braille character can be used as a 4x2 small bitmap. If it can be well organized and arranged in a certain way, it can spell a larger bitmap.

There are 2 ^ 8 = 256 different 4x2 bitmaps, and 256 different Braille characters. This means that there is a one-to-one correspondence between Braille characters and 4x2 bitmaps. In order to facilitate the transformation between Braille and bitmap, we need to design a coding scheme.

The table listed above is obviously well organized, and it can be found that the arrangement of Braille characters is very regular. The process of finding rules is omitted. Here is only the coding scheme. After the following operations, it can be ensured that Braille characters and their corresponding 4x2 bitmap have the same number:

Braille: number the Braille in the above table from top to bottom and from left to right from 0 to 255.

Bitmap: consider making a weight table:

1  8
2  16
4  32
64 128

Add up the weights of all corresponding bitmap black positions in the table, and the sum is the number of bitmap.

For example, the character "⢫" is numbered 171 and its bitmap is:

1 1
1 0
0 1
0 1

And the weight table py, 1 + 8 + 2 + 32 + 128 = 171, which is consistent with the expected result.

Using this method, a 4x2 small bitmap can be mapped to the number of its corresponding Braille characters. So we can draw pictures at the terminal.

realization

First, make a table of Braille characters:

const char *magic_table[] = {
	"⠀", "⠁", "⠂", "⠃", "⠄", "⠅", "⠆", "⠇", "⠈", "⠉", "⠊", "⠋", "⠌", "⠍", "⠎", "⠏",
	"⠐", "⠑", "⠒", "⠓", "⠔", "⠕", "⠖", "⠗", "⠘", "⠙", "⠚", "⠛", "⠜", "⠝", "⠞", "⠟",
	"⠠", "⠡", "⠢", "⠣", "⠤", "⠥", "⠦", "⠧", "⠨", "⠩", "⠪", "⠫", "⠬", "⠭", "⠮", "⠯",
	"⠰", "⠱", "⠲", "⠳", "⠴", "⠵", "⠶", "⠷", "⠸", "⠹", "⠺", "⠻", "⠼", "⠽", "⠾", "⠿",

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

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

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

Then implement the canvas structure. Here, the unsigned char array is used as a bool array. In future optimization, you can use bitmap to save space.

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);
}

Realize the functions of drawing pixels and printing:

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');
	}
}

The implementation is complete. The following is a description of function functions and parameters:

int canvas_init(canvas *p, int width, int height); take p Initialize to wide width high height Canvas of
void canvas_clear(canvas p); Destroy canvas p
void canvas_draw(canvas p, int x, int y); stay p of (x, y) Draw a pixel on the position
void canvas_erase(canvas p, int x, int y); Erase p in (x, y) Pixels in position
int canvas_test(canvas p, int x, int y); return p in (x, y) Has it been painted on the
void canvas_print(canvas p); Print p

Achieve the effect in the example

Use the convert command of ImageMagick to convert the picture file into an xpm file with only two colors, write a fool xpm parser, and simply process it with the above code to get the effect in the example. For the code of fool parser, see: doxpm.c .

On this machine, the commands to realize the example effect are:

$ convert -colors 2 sample.png a.xpm
$ gcc doxpm.c -o doxpm
$ ./doxpm
$ # If color is required:
$ ./doxpm | lolcat

finish

The code is only used to illustrate ideas, not to write a usable library. So the code wind is slightly fast and rough. Please forgive me.

Thanks zrz_orz taught me to contribute to the Logue daily and put forward a lot of modification suggestions.