← back to Penrose Designs

generator.py

49 lines

#!/usr/bin/env python3
# Penrose Tiling GENERATOR for Photoshop — decompose ONE tiling into recolorable component layers.
# Layers: Background / Thin rhombi / Fat rhombi / Seams  -> edit each layer's color to "generate" a colorway.
import cmath, math
from PIL import Image, ImageDraw
PHI=(1+5**0.5)/2
def subdivide(t):
    o=[]
    for c,A,B,C in t:
        if c==0:
            P=A+(B-A)/PHI; o+=[(0,C,P,B),(1,P,C,A)]
        else:
            Q=B+(A-B)/PHI; R=B+(C-B)/PHI; o+=[(1,R,C,A),(1,Q,R,B),(0,R,Q,A)]
    return o
def wheel():
    t=[]
    for i in range(10):
        B=cmath.rect(1,(2*i-1)*math.pi/10); C=cmath.rect(1,(2*i+1)*math.pi/10)
        if i%2==0: B,C=C,B
        t.append((0,0j,B,C))
    return t
def build(d):
    t=wheel()
    for _ in range(d): t=subdivide(t)
    return t
def hx(h): h=h.lstrip('#'); return tuple(int(h[i:i+2],16) for i in (0,2,4))

SIZE=3000; DEPTH=7; ROT=0.0
FAT=hx("#C9A24B"); THIN=hx("#D8CCB4"); SEAM=hx("#2B2622"); BG=hx("#EDE7DA")  # default "Oatmeal & Brass" colorway
tris=build(DEPTH)
r=cmath.rect(1,ROT); R=max(abs(z) for _,A,B,C in tris for z in (A,B,C))
sc=SIZE*0.82/R; cx=cy=SIZE/2
def P(z):
    z=z*r; return (cx+z.real*sc, cy+z.imag*sc)
ew=max(2,int(SIZE/700))

def blank(): return Image.new("RGBA",(SIZE,SIZE),(0,0,0,0))
fat=blank(); thin=blank(); seam=blank()
df=ImageDraw.Draw(fat); dt=ImageDraw.Draw(thin); ds=ImageDraw.Draw(seam)
for c,A,B,C in tris:
    poly=[P(A),P(B),P(C)]
    (df if c==1 else dt).polygon(poly, fill=(FAT+(255,) if c==1 else THIN+(255,)))
for c,A,B,C in tris:
    ds.line([P(A),P(B)], fill=SEAM+(255,), width=ew); ds.line([P(A),P(C)], fill=SEAM+(255,), width=ew)
bg=Image.new("RGBA",(SIZE,SIZE),BG+(255,))
for name,im in [("bg",bg),("thin",thin),("fat",fat),("seam",seam)]:
    im.save(f"out/gen_{name}.png")
print(f"generator layers rendered @ {SIZE}px depth{DEPTH} tris={len(tris)}")