TriKuf
All ideas and code are placed in the public domain.
A trikuf
is a 3x3 tile that conforms to the
basic rules of square geometric kufic calligraphy. It
is inspired by the kuf project.
The basic rule is: given any 2x2 tile quad ABCD (left to right, top to bottom), cannot have the following patterns: 0000 (0x00), 0110 (0x6), 1001 (0x9), 1111 (0xF).
This C program below will generate all the possible 3x3 tiles, and filter out the tiles that do not conform to square kufic. The valid tiles will be printed to standard output.
In addition to being technically correct square kufic, the trikufs selected to be printed also do not have any empty columns. This is an extra constraint added for aesthetic reasons. The idea behind this was to create a set of glyphs that could be more visually distinct on the horizontal axis when separated by whitespace. (And the reason why I want to do build glyphs like this is because I'm designing a symbolic programming language and VM for the monome grid).
If USE_BTPRNT
is defined, it will
also use btprnt to generate a pretty PBM that
looks like this:
To begin, the top level code.
#include <stdio.h>
#include <stdint.h>
#include "btprnt/btprnt.h"
<<make_quad>>
<<violation_quad>>
<<trirow>>
<<trirow_get>>
<<triglyph_to_trirows>>
<<trikuf>>
<<print>>
<<noblankcols>>
#ifdef USE_BTPRNT
int drawglyph(btprnt_region *reg, uint16_t g, int pos)
{
int x, y;
int offx, offy;
offx = 2 + (pos % 14) * 19;
offy = 2 + (pos / 14) * 19;
btprnt_draw_rect(reg, offx, offy, 16, 16, 1);
offx += 2;
offy += 2;
for (y = 0; y < 3; y++) {
uint8_t r;
r = g >> 3*y;
for (x = 0; x < 3; x++) {
if (r & (1 << x)) {
btprnt_draw_rect_filled(reg,
offx + 4*x,
offy + 4*y, 4, 4, 1);
}
}
}
}
#endif
int main(int argc, char *argv[])
{
uint16_t g;
uint16_t gmax;
int count;
#ifdef USE_BTPRNT
btprnt *bp;
btprnt_region r;
#endif
int w, h;
w = 4 + 14 * 19;
h = 4 + 7 * 19;
#ifdef USE_BTPRNT
bp = btprnt_new(w, h);
btprnt_region_init(btprnt_canvas_get(bp),
&r, 2, 2,
w - 4, h - 4);
#endif
gmax = (1 << 9) - 1;
count = 0;
for (g = 0; g <= gmax; g++) {
if (trikuf(g) && noblankcols(g)) {
print_triglyph(g);
#ifdef USE_BTPRNT
drawglyph(&r, g, count);
#endif
count++;
}
}
#ifdef USE_BTPRNT
btprnt_buf_pbm(btprnt_buf_get(bp), "out.pbm");
btprnt_del(&bp);
#endif
printf("%d total trikufs\n", count);
return 0;
}
A quad is a set of 4 tiles A, B, C, and D, which gets
represented 4 bits in a binary value. mkquad
will
construct one of these quads.
uint8_t mkquad(uint8_t a, uint8_t b, uint8_t c, uint8_t d)
{
return (a & 1) | (b & 1) << 1 | (c & 1) << 2 | (d & 1) << 3;
}
The binary representation fo the quad allows to quickly check if a quad is a so-called "violation" pattern.
int violation(uint8_t q)
{
return q == 0x00 || q == 0x6 || q == 0x9 || q == 0xF;
}
A trirow
is a set of 3 tiles aligned horizontally,
represented as 3 bits in an 8-bit integer.
A trirow can be created with mktrirow
,
with bits x
, y
, and z
(which correspond from
left to right).
uint8_t mktrirow(uint8_t x, uint8_t y, uint8_t z)
{
return (x & 1) | (y & 1) << 1 | (z & 1) << 2;
}
For a given row, a bit at position p
can be retrieved.
This is expected to be in range 0-2.
uint8_t trirow_get(uint8_t r, int p)
{
return (r & (1 << p)) >> p;
}
A 3x3 set of tiles is referred here as a triglyph
, stored
as 9 bits inside of a 16-bit integer. The bits of a triglyph
can be broken up into 3 trirows
, which will be called a
,
b
, and c
. This is done with the function triglyph2trirows
.
void triglyph2trirows(uint16_t g, uint8_t *a, uint8_t *b, uint8_t *c)
{
*a = g & 0x7;
g >>= 3;
*b = g & 0x7;
g >>= 3;
*c = g & 0x7;
}
These operations defined above can now be used to create the trikuf checker.
A triglyph g
can be represented as 3 trirows top-to-bottom
a
, b
, c
, each consisting of 3 bits:
A position a(0)
would indicate the first bit at row a
.
A map of the layout:
a 0 1 2
b 0 1 2
c 0 1 2
Up to 4 quads will need to be checked for violations. Using they notation described above, the quads are the following (moving left to right, top to bottom):
a(0) a(1) b(0) b(1)
a(1) a(2) b(1) b(2)
b(0) b(1) c(0) c(1)
b(1) b(2) c(1) c(2)
int check(uint8_t a, uint8_t b,
uint8_t w, uint8_t x, uint8_t y, uint8_t z)
{
return violation(mkquad(
trirow_get(a, w),
trirow_get(a, x),
trirow_get(b, y),
trirow_get(b, z)));
}
int trikuf(uint16_t g)
{
uint8_t a, b, c;
int rc;
a = b = c = 0;
triglyph2trirows(g, &a, &b, &c);
rc = check(a, b, 0, 1, 0, 1);
if (rc) return 0;
rc = check(a, b, 1, 2, 1, 2);
if (rc) return 0;
rc = check(b, c, 0, 1, 0, 1);
if (rc) return 0;
rc = check(b, c, 1, 2, 1, 2);
if (rc) return 0;
return 1;
}
The function print_triglyph
is used to print a triglyph to
standard output, using '#' to represent a filled tile,
and '-' to represent an empty tile. The function will
also place a little border around the triglyph as well.
void print_triglyph(uint16_t g)
{
int x, y;
printf("%x:\n", g);
printf("+---+\n");
for (y = 0; y < 3; y++) {
uint8_t r;
r = g >> 3*y;
putchar('|');
for (x = 0; x < 3; x++) {
if (r & (1 << x)) {
putchar('#');
} else {
putchar('-');
}
}
printf("|\n");
}
printf("+---+\n");
}
An additional constraint added to this program is to check
that a glyph has no empty columns. This is done with
the function noblankcols
.
int checkcol(uint8_t a, uint8_t b, uint8_t c, int pos)
{
return trirow_get(a, pos) == 0 &&
trirow_get(b, pos) == 0 &&
trirow_get(c, pos) == 0;
}
int noblankcols(uint16_t g)
{
uint8_t a, b, c;
int rc;
a = b = c = 0;
triglyph2trirows(g, &a, &b, &c);
rc = checkcol(a, b, c, 0);
if (rc) return 0;
rc = checkcol(a, b, c, 1);
if (rc) return 0;
rc = checkcol(a, b, c, 2);
if (rc) return 0;
return 1;
}