import numpy as np
from PIL import Image, ImageDraw, ImageFont
import networkx as nx
from itertools import product
import random
# --- Enhanced: Generate a 4096×4096 image using every 24-bit RGB color once,
# with bold labels, a shuffled gray-looking background, and 75% transparent bg.
# --- 1. Build the graph ----------------------------------------
alphabet = ['A', 'B']
two_letter = [''.join(p) for p in product(alphabet, repeat=2)]
four_letter = [''.join(p) for p in product(alphabet, repeat=4)]
G = nx.DiGraph()
G.add_nodes_from(four_letter)
for state in four_letter:
for i in range(3):
for tgt in two_letter:
new_state = state[:i] + tgt + state[i+2:]
G.add_edge(state, new_state)
from networkx.algorithms.dag import transitive_closure
TC = transitive_closure(G)
# --- 2. Layout -------------------------------------------------
W = H = 4096
pos = nx.circular_layout(TC, scale=min(W, H) * 0.4, center=(W/2, H/2))
# --- 3. Render shapes & text to masks -------------------------
mask_shape = Image.new('1', (W, H), 0)
draw_shape = ImageDraw.Draw(mask_shape)
mask_label = Image.new('1', (W, H), 0)
draw_label = ImageDraw.Draw(mask_label)
# edges
for u, v in TC.edges():
x1, y1 = pos[u]
x2, y2 = pos[v]
draw_shape.line([(x1, y1), (x2, y2)], fill=1)
# nodes
r = 3
for _, (x, y) in pos.items():
draw_shape.ellipse([x-r, y-r, x+r, y+r], fill=1)
# labels with DejaVuSansBold size 48
try:
font = ImageFont.truetype("DejaVuSans-Bold.ttf", size=48)
except IOError:
font = ImageFont.load_default()
for node, (x, y) in pos.items():
txt = node
tx, ty = x + r + 5, y - 24
draw_label.text((tx, ty), txt, font=font, fill=1)
shape_mask = np.array(mask_shape, dtype=bool)
label_mask = np.array(mask_label, dtype=bool)
shape_only = shape_mask & ~label_mask
bg_mask = ~(shape_mask | label_mask)
N = W * H
shape_idx = np.flatnonzero(shape_only.ravel())
label_idx = np.flatnonzero(label_mask.ravel())
bg_idx = np.flatnonzero(bg_mask.ravel())
assert len(shape_idx) + len(label_idx) + len(bg_idx) == N
# --- 4. Prepare color pools --------------------------
all_colors = np.arange(N, dtype=np.uint32)
r_ch = (all_colors >> 16) & 0xFF
g_ch = (all_colors >> 8) & 0xFF
b_ch = all_colors & 0xFF
lum = 0.2126*r_ch + 0.7152*g_ch + 0.0722*b_ch
sorted_idx = np.argsort(lum)
n_shape = len(shape_idx)
n_label = len(label_idx)
# brightest for shapes
bright_pool = sorted_idx[-n_shape:]
# darkest for labels + background
dark_pool = sorted_idx[: n_label + (N - n_shape - n_label)]
label_colors = dark_pool[:n_label]
bg_colors = dark_pool[n_label:]
# shuffle background pool for uniform gray appearance
random.shuffle(bg_colors)
# --- 5. Assemble RGBA image ----------------------------
img_arr = np.zeros((H*W, 4), dtype=np.uint8)
def unpack(c):
return ((c >> 16) & 0xFF, (c >> 8) & 0xFF, c & 0xFF)
# labels: darkest, fully opaque
for i, pix in enumerate(label_idx):
r_, g_, b_ = unpack(label_colors[i])
img_arr[pix] = (r_, g_, b_, 255)
# background: next darkest, 75% opacity
alpha_bg = int(0.75 * 255)
for i, pix in enumerate(bg_idx):
r_, g_, b_ = unpack(bg_colors[i])
img_arr[pix] = (r_, g_, b_, alpha_bg)
# shapes: brightest, fully opaque
for i, pix in enumerate(shape_idx):
r_, g_, b_ = unpack(bright_pool[i])
img_arr[pix] = (r_, g_, b_, 255)
# reshape & save
img_arr = img_arr.reshape((H, W, 4))
img = Image.fromarray(img_arr, 'RGBA')
img.save('reachability_color_graph_4096_v2.png')
print("Saved RGBA 4096×4096 image with every 24-bit RGB color once, bg at 75% opacity.")