

This is an evolution of E8 v1. Made with Chatgpt and Google Colab.
import numpy as np import colorsys import math import random from itertools import product from PIL import Image, ImageDraw, ImageFont # === Configuration === SIZE = 4096 TOTAL_PIXELS = SIZE * SIZE TOTAL_COLORS = 2**24 # 16,777,216 ALPHA_BG = int(255 * 0.25) # 25% opacity (75% transparent) ALPHA_FG = 255 # fully opaque for shapes # Dot radii per ring (inner → outer) RADIUS_MAP = [10, 9, 8, 7, 6, 5, 4, 3] # Simple roots for Coxeter element simple_roots = np.array([ [ 1,-1, 0, 0, 0, 0, 0, 0], [ 0, 1,-1, 0, 0, 0, 0, 0], [ 0, 0, 1,-1, 0, 0, 0, 0], [ 0, 0, 0, 1,-1, 0, 0, 0], [ 0, 0, 0, 0, 1,-1, 0, 0], [ 0, 0, 0, 0, 0, 1,-1, 0], [ 0, 0, 0, 0, 0, 0, 1,-1], [ .5,.5,.5,.5,.5,.5,.5,-.5] ], dtype=float) # === 1) Build the 240 E8 roots === roots = [] for i in range(8): for j in range(i+1, 8): v = np.zeros(8) v[i], v[j] = 1, -1 roots.extend([v.copy(), (-v).copy()]) v[i], v[j] = 1, 1 roots.extend([v.copy(), (-v).copy()]) for signs in product([1, -1], repeat=8): if np.prod(signs) == 1: roots.append(np.array(signs, float) * 0.5) roots = np.vstack(roots) # === 2) Compute Coxeter element === def reflect(alpha): α = alpha.reshape(8,1) return np.eye(8) - 2 * (α @ α.T) / (α.T @ α) C = np.eye(8) for α in simple_roots: C = reflect(α) @ C # === 3) Petrie-plane basis from primitive eigenvector === h = 30 eigvals, eigvecs = np.linalg.eig(C) target = np.exp(2j * np.pi / h) idx = np.argmin(np.abs(eigvals - target)) v = eigvecs[:, idx] B = np.vstack([v.real, v.imag]).T # (8,2) # === 4) Project to pixel coordinates === proj = roots @ B margin = 0.05 * SIZE scale = (SIZE/2 - margin) / np.max(np.linalg.norm(proj, axis=1)) coords_f = proj * scale + SIZE/2 coords = np.round(coords_f).astype(int) # === 5) Ring assignment === angles = np.angle(proj[:,0] + 1j*proj[:,1]) % (2*np.pi) exponents = np.array([1,7,11,13,17,19,23,29]) ideal = (2*np.pi * exponents / h) % (2*np.pi) ring_idx = np.argmin(np.abs(angles.reshape(-1,1) - ideal), axis=1).astype(int) # === 6) Reserve text label pixels first === used = np.zeros((SIZE, SIZE), bool) font = ImageFont.truetype("DejaVuSans.ttf", 48) label_fill, label_outline = [], [] for k, text in enumerate(["I","II","III","IV","V","VI","VII","VIII"]): pts = [coords[i] for i in range(240) if ring_idx[i]==k] cx, cy = np.round(np.mean(pts, axis=0)).astype(int) mask = Image.new("1", (SIZE, SIZE)); dmask = ImageDraw.Draw(mask) dmask.text((cx, cy), text, font=font, fill=1) arr = np.array(mask, bool) # outline via dilation pad = np.pad(arr, ((1,1),(1,1)), mode='constant', constant_values=False) dil = np.zeros_like(arr) for dy in (-1,0,1): for dx in (-1,0,1): if dx == 0 and dy == 0: continue dil |= pad[1+dy:1+dy+SIZE, 1+dx:1+dx+SIZE] out = dil & ~arr ys, xs = np.where(out) for y, x in zip(ys, xs): label_outline.append((x, y)); used[y, x] = True ys, xs = np.where(arr) for y, x in zip(ys, xs): label_fill.append((x, y)); used[y, x] = True # === 7) Collect shape and background pixels === # 7a) Edges, Dots, Halos (same as before)... # 7b) ... # 7c) Boundaries – define mid_circle here so it is available # Midpoint circle algorithm for ring outlines def mid_circle(cx, cy, r): x, y, err = r, 0, 0 pts = [] while x >= y: for dx, dy in [( x, y),( y, x),(-y, x),(-x, y),(-x,-y),(-y,-x),( y,-x),( x,-y)]: pts.append((cx+dx, cy+dy)) y += 1 err += 1 + 2*y if 2*(err-x) + 1 > 0: x -= 1 err += 1 - 2*x return pts # Now collect shapes and background pixels line_px, dot_px, halo_px, bound_px = [], [], [], [] # Edges line_px, dot_px, halo_px, bound_px = [], [], [], [] # Edges gram = roots @ roots.T iu, ju = np.where(np.triu(np.isclose(gram, 1.0), k=1)) def bres(x0, y0, x1, y1): dx, sx = abs(x1-x0), (1 if x0
-dy: err -= dy; x0 += sx if e2 < dx: err += dx; y0 += sy return pts for i, j in zip(iu, ju): for x, y in bres(*coords[i], *coords[j]): if not used[y, x]: line_px.append((x, y)); used[y, x] = True # Dots & halos circle_off = {k: [(dx, dy) for dy in range(-r, r+1) for dx in range(-int(math.sqrt(r*r-dy*dy)), int(math.sqrt(r*r-dy*dy))+1)] for k, r in enumerate(RADIUS_MAP)} for rid, (x0, y0) in enumerate(coords): k = ring_idx[rid] for dx, dy in circle_off[k]: x, y = x0+dx, y0+dy if not used[y, x]: dot_px.append((x, y)); used[y, x] = True R = RADIUS_MAP[k] + 1 for dy in range(-R, R+1): dxm = int(math.sqrt(R*R - dy*dy)) for dx in (-dxm, dxm): x, y = x0+dx, y0+dy if not used[y, x]: halo_px.append((x, y)); used[y, x] = True # Boundaries def mid_circle(cx, cy, r): x, y, err = r, 0, 0; pts = [] while x >= y: for dx, dy in [( x, y),( y, x),(-y, x),(-x, y),(-x,-y),(-y,-x),( y,-x),( x,-y)]: pts.append((cx+dx, cy+dy)) y += 1; err += 1 + 2*y if 2*(err-x) + 1 > 0: x -= 1; err += 1 - 2*x return pts cx, cy = SIZE//2, SIZE//2 for k in range(8): rad = int(round(np.linalg.norm(proj[ring_idx==k], axis=1).mean()*scale)) for x, y in mid_circle(cx, cy, rad): if not used[y, x]: bound_px.append((x, y)); used[y, x] = True # Background bg_px = [(x, y) for y in range(SIZE) for x in range(SIZE) if not used[y, x]] # === 8) Palette slicing & ordering === idxs = np.arange(TOTAL_COLORS, dtype=np.uint32) pal = np.empty((TOTAL_COLORS, 3), dtype=np.uint8) pal[:,0] = (idxs >> 16) & 0xFF; pal[:,1] = (idxs >> 8) & 0xFF; pal[:,2] = idxs & 0xFF hsv = np.array([colorsys.rgb_to_hsv(*(c/255.0)) for c in pal]) v = hsv[:,2]; s = hsv[:,1] # Label fill: brightest & saturated fill_idx = np.lexsort((-s, -v))[:len(label_fill)] # Label outline: darkest outline_idx = np.argsort(v)[:len(label_outline)] used_idx = set(fill_idx.tolist() + outline_idx.tolist()) # Remaining sorted for shapes by brightness+sat (bright start) remaining = [i for i in np.lexsort((-s, -v)) if i not in used_idx] i = 0 line_idx = remaining[i:i+len(line_px)]; i += len(line_px) halo_idx = remaining[i:i+len(halo_px)]; i += len(halo_px) dot_idx = remaining[i:i+len(dot_px)]; i += len(dot_px) bound_idx = remaining[i:i+len(bound_px)]; i += len(bound_px) bg_idx = remaining[i:i+len(bg_px)]; i += len(bg_px) assert i == len(remaining) # Extract colors fill_cols = pal[fill_idx]; outline_cols = pal[outline_idx] line_cols = pal[line_idx]; halo_cols = pal[halo_idx] dot_cols = pal[dot_idx]; bound_cols = pal[bound_idx] bg_cols = pal[bg_idx] # Completely shuffle background noise-style pair_bg = list(zip(bg_px, bg_cols)); random.shuffle(pair_bg) bg_px, bg_cols = zip(*pair_bg) # === 9) Render layer-by-layer === img = Image.new("RGBA", (SIZE, SIZE), (0,0,0,255)); px = img.load() # 1) background (25% opaque) for (x, y), (r, g, b) in zip(bg_px, bg_cols): px[x, y] = (r, g, b, ALPHA_BG) # 2) boundaries for (x, y), (r, g, b) in zip(bound_px, bound_cols): px[x, y] = (r, g, b, ALPHA_FG) # 3) lines (bright) for (x, y), (r, g, b) in zip(line_px, line_cols): px[x, y] = (r, g, b, ALPHA_FG) # 4) halos for (x, y), (r, g, b) in zip(halo_px, halo_cols): px[x, y] = (r, g, b, int(ALPHA_FG * 0.3)) # 5) dots for (x, y), (r, g, b) in zip(dot_px, dot_cols): px[x, y] = (r, g, b, ALPHA_FG) # 6) label outline for (x, y), (r, g, b) in zip(label_outline, outline_cols): px[x, y] = (r, g, b, ALPHA_FG) # 7) label fill (topmost) for (x, y), (r, g, b) in zip(label_fill, fill_cols): px[x, y] = (r, g, b, ALPHA_FG) # === 10) Save === img.save("E8_Petrie_exact24bit_final_4096.png") print("✅ Done: background fully shuffled, labels unobstructed, bright lines, all colors used.")
Date | |
---|---|
Colors | 16,777,216 |
Pixels | 16,777,216 |
Dimensions | 4,096 × 4,096 |
Bytes | 13,405,694 |