Skip to content

Instantly share code, notes, and snippets.

@katef
Last active September 8, 2024 05:59
Show Gist options
  • Save katef/59450aa622315bd35fc27bd383c2dbe6 to your computer and use it in GitHub Desktop.
Save katef/59450aa622315bd35fc27bd383c2dbe6 to your computer and use it in GitHub Desktop.
XBM to UTF-8 braille image things
/*
* John Conway's Game of Life.
*
* This is written for POSIX, using Curses. Resizing of the terminal is not
* supported.
*
* By convention in this program, x is the horizontal coordinate and y is
* vertical. There correspond to the width and height respectively.
* The current generation number is illustrated when show_generation is set.
*
* You'll need ncurses to compile. On debian this is packaged as:
*
* $ apt-get install libncurses-dev
*
* One way to make a suitable image:
*
* $ convert -scale x100 snorkmaiden.png /tmp/snorkmaiden.xbm
*
* I didn't write runtime image loading code. Images are given at compile time.
* XBM is very simple, and the source is an array in C code.
* Inside an .xbm file looks something like this, where the data is named
* after the filename (in this case snorkmaiden.xbm):
*
* #define snorkmaiden_width 57
* #define snorkmaiden_height 61
* static char snorkmaiden_bits[] = { 0xab, 0xcd, ... }
*
* To compile you need to pass both the filename and the name for the variables,
* sorry about that.
*
* $ gcc -DIMAGE_NAME=snorkmaiden -DIMAGE_XBM='"/tmp/snorkmaiden.xbm"' life-utf8.c -lncursesw
*
* You can record your terminal to an animated gif by running
* xwininfo to find the coordinates for your window, and then:
*
* byzanz-record -d 30 -x 1056 -y 20 -w 864 -h 795 /tmp/x.gif
*
* Public domain, have fun.
* Katherine Flavel, @thingskatedid
*/
#define _XOPEN_SOURCE 500
#include <curses.h>
#include <unistd.h>
#include <assert.h>
#include <locale.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <stdio.h>
#include <errno.h>
#include <float.h>
#define paste_(a, b) a##b
#define paste(a, b) paste_(a, b)
#ifdef IMAGE_XBM
#include IMAGE_XBM
#endif
static unsigned w;
static unsigned h;
static unsigned char *board;
static int cursor; /* visibility */
/* Configurable things */
static int invert;
static int show_generation;
static int step;
static int persistence;
static double delay = 500000;
static double decay = 0.8;
static unsigned char *
cell(unsigned x, unsigned y)
{
return &board[y * w + x];
}
static void
utf8(unsigned cp, char c[])
{
if (cp <= 0x7f) {
c[0] = cp;
return;
}
if (cp <= 0x7ff) {
c[0] = (cp >> 6) + 192;
c[1] = (cp & 63) + 128;
return;
}
if (0xd800 <= cp && cp <= 0xdfff) {
/* invalid */
goto error;
}
if (cp <= 0xffff) {
c[0] = (cp >> 12) + 224;
c[1] = ((cp >> 6) & 63) + 128;
c[2] = (cp & 63) + 128;
return;
}
if (cp <= 0x10ffff) {
c[0] = (cp >> 18) + 240;
c[1] = ((cp >> 12) & 63) + 128;
c[2] = ((cp >> 6) & 63) + 128;
c[3] = (cp & 63) + 128;
return;
}
error:
fprintf(stderr, "codepoint out of range\n");
exit(1);
}
static void
print(const unsigned char *board, unsigned long g)
{
unsigned x;
unsigned y;
struct {
int x;
int y;
int m;
} braille[] = {
{ 0, 0, 0x2801 }, { 1, 0, 0x2808 },
{ 0, 1, 0x2802 }, { 1, 1, 0x2810 },
{ 0, 2, 0x2804 }, { 1, 2, 0x2820 },
{ 0, 3, 0x2840 }, { 1, 3, 0x2880 }
};
for (y = 0; y < h - 3; y += 4) {
for (x = 0; x < w - 1; x += 2) {
char s[5] = { 0x00, 0x00, 0x00, 0x00, 0x00 };
size_t i;
int cp;
cp = 0x2800; /* blank */
for (i = 0; i < sizeof braille / sizeof *braille; i++) {
if (*cell(x + braille[i].x, y + braille[i].y)) {
cp |= braille[i].m;
}
}
utf8(cp, s);
mvaddstr(y / 4, x / 2, s);
}
}
if (show_generation) {
mvprintw(0, 0, "%ld ", g);
}
refresh();
}
static int
xpm_pixel(unsigned x, unsigned y, unsigned width, unsigned height, const unsigned char *img)
{
unsigned byte, bit;
/* xpm has eight bits per element. Rows are padded to a byte's width */
width += 7;
width &= ~7;
byte = (y * width + x) / 8;
bit = (y * width + x) % 8;
return !!(img[byte] & (1U << bit));
}
static void
fill_img(unsigned char *board, unsigned width, unsigned height, const void *img)
{
unsigned x, y;
int pixel;
assert(width <= w);
assert(width <= h);
assert(img != NULL);
for (y = 0; y < height; y++) {
for (x = 0; x < width; x++) {
pixel = xpm_pixel(x, y, width, height, img);
if (invert) {
pixel = !pixel;
}
if (pixel) {
*cell(x, y) = pixel;
}
}
}
}
static void
fill_rpentomino(unsigned char *board)
{
unsigned x;
unsigned y;
unsigned i;
struct {
int x;
int y;
} r[] = {
{ 1, 0 }, { 2, 0 },
{ 0, 1 }, { 1, 1 },
{ 1, 2 }
};
for (i = 0; i < sizeof r / sizeof *r; i++) {
x = r[i].x + w / 2;
y = r[i].y + h / 2;
*cell(x, y) = 1;
}
}
static void
fill(unsigned char *board)
{
(void) fill_img;
(void) fill_rpentomino;
#ifndef IMAGE_XBM
fill_rpentomino(board);
#else
fill_img(board, paste(IMAGE_NAME, _width), paste(IMAGE_NAME, _height), paste(IMAGE_NAME, _bits));
#endif
}
static unsigned
wrap(unsigned range, int i)
{
if (i < 0) {
i += range;
}
return i % range;
}
static void
update(unsigned char *board)
{
unsigned x;
unsigned y;
struct {
int x;
int y;
} moore[] = {
{ -1, -1 }, { 0, -1 }, { 1, -1 },
{ -1, 0 }, { 1, 0 },
{ -1, 1 }, { 0, 1 }, { 1, 1 }
};
for (y = 0; y < h; y++) {
for (x = 0; x < w; x++) {
unsigned char v;
unsigned count;
unsigned n;
count = 0;
/* count neighbouring cells */
for (n = 0; n < sizeof moore / sizeof *moore; n++) {
unsigned nx = wrap(w, x + moore[n].x);
unsigned ny = wrap(h, y + moore[n].y);
count += *cell(nx, ny) & 1;
}
switch (count) {
case 0:
case 1: v = 0; break;
case 2: v = *cell(x, y); break;
case 3: v = 1; break;
default: v = 0; break;
}
*cell(x, y) |= (v << 1);
}
}
/* move the new generation over the previous */
for (y = 0; y < h; y++) {
for (x = 0; x < w; x++) {
*cell(x, y) >>= 1;
}
}
}
static void
cleanup(void)
{
free(board);
curs_set(cursor);
echo();
}
int
main(int argc, char *argv[])
{
unsigned long g;
if (argc > 1) {
fprintf(stderr, "usage: life\n");
return 1;
}
if (!setlocale(LC_CTYPE, "C.UTF-8")) {
perror("C.UTF-8");
return 1;
}
initscr();
w = (unsigned) COLS * 2;
h = (unsigned) LINES * 4;
noecho();
cursor = curs_set(0);
atexit(cleanup);
#ifdef IMAGE_XBM
if (paste(IMAGE_NAME, _width) > w || paste(IMAGE_NAME, _height) > h) {
fprintf(stderr, "%s: Image dimensions must be %ux%u max\n", IMAGE_XBM, w, h);
exit(1);
}
#endif
board = calloc(w * h, 1);
if (board == NULL) {
perror("malloc");
return 1;
}
/* to sleep for a short period of time in getch() */
timeout(10);
cbreak();
clear();
fill(board);
for (g = 1; ; g++) {
print(board, g);
if (g == 1 && delay > DBL_EPSILON) {
usleep(delay);
}
if (decay > DBL_EPSILON && delay > DBL_EPSILON) {
delay *= decay;
usleep(delay);
}
if (step) {
getchar();
} else if (getch() != ERR) {
return 0;
}
update(board);
if (persistence) {
fill(board);
}
}
return 0;
}
/*
* Just the XBM image conversion, without the Game of Life stuff.
*
* One way to make a suitable image:
*
* $ convert -scale x100 snorkmaiden.png /tmp/snorkmaiden.xbm
*
* I didn't write runtime image loading code. Images are given at compile time.
* XBM is very simple, and the source is an array in C code.
* Inside an .xbm file looks something like this, where the data is named
* after the filename (in this case snorkmaiden.xbm):
*
* #define snorkmaiden_width 57
* #define snorkmaiden_height 61
* static char snorkmaiden_bits[] = { 0xab, 0xcd, ... }
*
* To compile:
*
* $ gcc -DIMAGE_NAME=snorkmaiden -DIMAGE_XBM='"/tmp/snorkmaiden.xbm"' xbm-utf8.c
*
* Public domain.
* Katherine Flavel, @thingskatedid
*/
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#define paste_(a, b) a##b
#define paste(a, b) paste_(a, b)
#ifndef IMAGE_NAME
#error Compile with -DIMAGE_NAME=xyz
#endif
#ifndef IMAGE_XBM
#error Compile with -DIMAGE_XBM='"/tmp/xyz.xbm"'
#endif
#ifdef IMAGE_XBM
#include IMAGE_XBM
#endif
static unsigned w;
static unsigned h;
static unsigned char *board;
/* Configurable things */
static int invert;
static unsigned char *
cell(unsigned x, unsigned y)
{
return &board[y * w + x];
}
static void
utf8(unsigned cp, char c[])
{
if (cp <= 0x7f) {
c[0] = cp;
return;
}
if (cp <= 0x7ff) {
c[0] = (cp >> 6) + 192;
c[1] = (cp & 63) + 128;
return;
}
if (0xd800 <= cp && cp <= 0xdfff) {
/* invalid */
goto error;
}
if (cp <= 0xffff) {
c[0] = (cp >> 12) + 224;
c[1] = ((cp >> 6) & 63) + 128;
c[2] = (cp & 63) + 128;
return;
}
if (cp <= 0x10ffff) {
c[0] = (cp >> 18) + 240;
c[1] = ((cp >> 12) & 63) + 128;
c[2] = ((cp >> 6) & 63) + 128;
c[3] = (cp & 63) + 128;
return;
}
error:
fprintf(stderr, "codepoint out of range\n");
exit(1);
}
static void
print(const unsigned char *board)
{
unsigned x;
unsigned y;
struct {
int x;
int y;
int m;
} braille[] = {
{ 0, 0, 0x2801 }, { 1, 0, 0x2808 },
{ 0, 1, 0x2802 }, { 1, 1, 0x2810 },
{ 0, 2, 0x2804 }, { 1, 2, 0x2820 },
{ 0, 3, 0x2840 }, { 1, 3, 0x2880 }
};
for (y = 0; y < h - 3; y += 4) {
for (x = 0; x < w - 1; x += 2) {
char s[5] = { 0x00, 0x00, 0x00, 0x00, 0x00 };
size_t i;
int cp;
cp = 0x2800; /* blank */
for (i = 0; i < sizeof braille / sizeof *braille; i++) {
if (*cell(x + braille[i].x, y + braille[i].y)) {
cp |= braille[i].m;
}
}
utf8(cp, s);
fputs(s, stdout);
}
fputc('\n', stdout);
}
}
static int
xpm_pixel(unsigned x, unsigned y, unsigned width, unsigned height, const unsigned char *img)
{
unsigned byte, bit;
/* xpm has eight bits per element. Rows are padded to a byte's width */
width += 7;
width &= ~7;
byte = (y * width + x) / 8;
bit = (y * width + x) % 8;
return !!(img[byte] & (1U << bit));
}
static void
fill_img(unsigned char *board, unsigned width, unsigned height, const void *img)
{
unsigned x, y;
int pixel;
assert(img != NULL);
for (y = 0; y < height; y++) {
for (x = 0; x < width; x++) {
pixel = xpm_pixel(x, y, width, height, img);
if (invert) {
pixel = !pixel;
}
if (pixel) {
*cell(x, y) = pixel;
}
}
}
}
int
main(int argc, char *argv[])
{
unsigned long g;
if (argc > 1) {
fprintf(stderr, "usage: xbm\n");
return 1;
}
w = paste(IMAGE_NAME, _width);
h = paste(IMAGE_NAME, _height);
board = calloc(w * h, 1);
if (board == NULL) {
perror("malloc");
return 1;
}
fill_img(board, w, h, paste(IMAGE_NAME, _bits));
print(board);
free(board);
return 0;
}
@katef
Copy link
Author

katef commented Jul 3, 2020

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment