Skip to content

Instantly share code, notes, and snippets.

@nst
Created June 9, 2022 16:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nst/b6dbead217ea2f43c45c91271c628eac to your computer and use it in GitHub Desktop.
Save nst/b6dbead217ea2f43c45c91271c628eac to your computer and use it in GitHub Desktop.
# !/usr/bin/env python3
# Nicolas Seriot
# 2022-06-09
# Reproducing Roni Kaufman's art
# https://twitter.com/KaufmanRoni/status/1520372880156073984
# Sample output: https://seriot.ch/visualization/truchet.png
import cairo
import math
import random
NB_COLS = 16
NB_ROWS = 16
MARGIN = 128
W = 128
GRID = False
M_PI_2 = math.pi/2.0
P = ((1/255., 26/255., 92/255.), # background, default color
(255/255.,212/255.,0/255.), # yellow
(220/255.,6/255.,14/255.), # red
(0/255.,100/255.,177/255.), # blue
(1,1,1))
FILLED = set()
class Tile:
# color indices in global palette P
bg = 0
nw = 0
ne = 0
sw = 0
se = 0
def colors_used(self):
colors = [self.bg, self.nw, self.ne, self.sw, self.se]
s = set(colors)
#s.remove(None)
return s
def fill(self, pos, color):
p = getattr(self, pos)
if p == 0:
setattr(self, pos, color)
elif p == None:
self.bg = color
def draw_arc(c, x, y, start_angle, fill_color):
c.save()
c.move_to(x, y)
c.set_source_rgb(*P[fill_color])
c.arc(x, y, W/2.0, start_angle, start_angle + math.pi/2.0)
c.fill()
c.set_source_rgb(*P[0])
c.set_line_width(12)
c.arc(x, y, W/2.0, start_angle, start_angle + math.pi/2.0)
c.stroke()
c.restore()
def draw_tile(c, t):
c.save()
if GRID:
c.save()
c.set_line_width(5)
c.rectangle(0, 0, W, W)
c.set_source_rgb(1,1,1)
c.stroke()
c.restore()
if t.bg != None:
c.set_source_rgb(*P[t.bg])
c.rectangle(0, 0, W, W)
c.fill()
if t.nw != None:
draw_arc(c, 0, 0, M_PI_2 * 0, t.nw)
if t.ne != None:
draw_arc(c, W, 0, M_PI_2 * 1, t.ne)
if t.se != None:
draw_arc(c, W, W, M_PI_2 * 2, t.se)
if t.sw != None:
draw_arc(c, 0, W, M_PI_2 * 3, t.sw)
c.restore()
def generate_tiles():
m = [[Tile() for c in range(NB_COLS)] for r in range(NB_ROWS)]
for c in range(NB_COLS):
# first row
t = m[0][c]
t.nw = None
t.ne = None
# last row
t = m[-1][c]
t.sw = None
t.se = None
for r in range(NB_ROWS):
# first col
t = m[r][0]
t.nw = None
t.sw = None
# last col
t = m[r][-1]
t.ne = None
t.se = None
# inner tiles
for c in range(1, NB_COLS-1):
for r in range(1, NB_ROWS-1):
t = m[r][c]
if random.choice([0, 1]) == 0:
t.nw = None
t.se = None
else:
t.ne = None
t.sw = None
return m
def fill(m, c, r, pos, color):
key = "%d-%d-%s-%d" % (c, r, pos, color)
if key in FILLED:
return
FILLED.add(key)
t = m[r][c]
t.fill(pos, color)
if pos == "ne":
fill(m, c+1, r, "nw", color)
fill(m, c, r-1, "se", color)
fill(m, c+1, r-1, "sw", color)
if t.ne == None:
fill(m, c, r, "sw", color)
elif pos == "se":
fill(m, c+1, r, "sw", color)
fill(m, c, r+1, "ne", color)
fill(m, c+1, r+1, "nw", color)
if t.se == None:
fill(m, c, r, "nw", color)
elif pos == "nw":
fill(m, c-1, r, "ne", color)
fill(m, c, r-1, "sw", color)
fill(m, c-1, r-1, "se", color)
if t.nw == None:
fill(m, c, r, "se", color)
elif pos == "sw":
fill(m, c-1, r, "se", color)
fill(m, c, r+1, "nw", color)
fill(m, c-1, r+1, "ne", color)
if t.sw == None:
fill(m, c, r, "ne", color)
def do_color_tiles(m):
for r in range(NB_ROWS):
for c in range(NB_COLS):
t = m[r][c]
colors_in_tile = t.colors_used()
candidates = list(set([1,2,3,4]) - colors_in_tile)
if t.nw == 0:
fill(m, c, r, "nw", random.choice(candidates))
if t.ne == 0:
fill(m, c, r, "ne", random.choice(candidates))
if t.se == 0:
fill(m, c, r, "se", random.choice(candidates))
if t.sw == 0:
fill(m, c, r, "sw", random.choice(candidates))
return None
def draw_png(filename):
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32,
W*NB_COLS + 2*MARGIN,
W*NB_ROWS + 2*MARGIN)
c = cairo.Context(surface)
c.set_source_rgb(*P[0])
c.paint()
c.translate(MARGIN, MARGIN)
m = generate_tiles()
do_color_tiles(m)
for row in range(NB_ROWS):
tiles_row = m[row]
for col in range(NB_COLS):
t = tiles_row[col]
c.save()
c.translate(col*W, row*W)
draw_tile(c, t)
c.restore()
surface.write_to_png(filename)
draw_png("truchet.png")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment