Created
June 9, 2022 16:08
-
-
Save nst/b6dbead217ea2f43c45c91271c628eac to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# !/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