#!/usr/bin/env python3
import numpy as np
from PIL import Image
from datetime import datetime
# ============================================================
# Progress Pride — all 24-bit RGB colors exactly once
# - Stripes allocated FIRST
# - Size: 8192x2048 (2:1)
# - Timestamped filename
# ============================================================
H, W = 2048, 8192
TOTAL = H * W # 16,777,216
# ------------------------------
# Geometry: stripes + chevron
# ------------------------------
y = np.arange(H, dtype=np.int32)
x = np.arange(W, dtype=np.int32)
Y, X = np.meshgrid(y, x, indexing='ij')
# Six equal-height horizontal stripes (R,O,Y,G,B,V)
stripe_idx = (Y * 6) // H # 0..5
# Chevron bands as nested 45° "V" shapes from the hoist
y_mid = (H - 1) / 2.0
t = X.astype(np.float32) + np.abs(Y.astype(np.float32) - y_mid)
# Set chevron total width to H so it occupies ~25% of the 2:1 flag width
W_total = float(H)
bands = 5
band_w = W_total / bands
# Order at the hoist: black, brown, light blue, pink, white
chev_levels = [(i * band_w, (i + 1) * band_w) for i in range(bands)]
chev_masks = []
for i in range(bands):
lo, hi = chev_levels[i]
m = (t < hi) & (t >= lo)
chev_masks.append(m)
# Mask for any chevron pixel (overlays stripes)
chev_any = np.zeros((H, W), dtype=bool)
for m in chev_masks:
chev_any |= m
# Stripe masks excluding chevron overlay
stripe_masks = []
for s in range(6):
stripe_masks.append((stripe_idx == s) & (~chev_any))
# ------------------------------
# Color pool: all unique RGBs
# ------------------------------
# Deterministic order: r major, then g, then b
r = np.repeat(np.arange(256, dtype=np.uint8), 256 * 256)
g = np.tile(np.repeat(np.arange(256, dtype=np.uint8), 256), 256)
b = np.tile(np.arange(256, dtype=np.uint8), 256 * 256)
colors = np.stack([r, g, b], axis=1) # (16777216, 3), uint8
# Normalize to [0,1] float32 for HSV computations
rf = colors[:, 0].astype(np.float32) / 255.0
gf = colors[:, 1].astype(np.float32) / 255.0
bf = colors[:, 2].astype(np.float32) / 255.0
# Vectorized RGB->HSV (H in [0,1), S,V in [0,1])
maxc = np.maximum(np.maximum(rf, gf), bf)
minc = np.minimum(np.minimum(rf, gf), bf)
v = maxc
c = maxc - minc
s = np.where(maxc > 0.0, c / np.maximum(maxc, 1e-12), 0.0)
h = np.zeros_like(v, dtype=np.float32)
mask = c > 1e-12
rc = np.zeros_like(v); gc = np.zeros_like(v); bc = np.zeros_like(v)
rc[mask] = (maxc[mask] - rf[mask]) / c[mask]
gc[mask] = (maxc[mask] - gf[mask]) / c[mask]
bc[mask] = (maxc[mask] - bf[mask]) / c[mask]
mr = (mask & (rf >= gf) & (rf >= bf))
mg = (mask & (gf > rf) & (gf >= bf))
mb = (mask & (bf > rf) & (bf > gf))
h[mr] = (bc[mr] - gc[mr]) / 6.0
h[mg] = (2.0 + (rc[mg] - bc[mg])) / 6.0
h[mb] = (4.0 + (gc[mb] - rc[mb])) / 6.0
h = h % 1.0
# Luma (Rec.709), useful for dark/light targeting
luma = 0.2126 * rf + 0.7152 * gf + 0.0722 * bf
# ------------------------------
# Region pixel counts
# ------------------------------
def count(mask: np.ndarray) -> int:
return int(np.count_nonzero(mask))
region_defs = {
# Stripes first
"stripe_red": stripe_masks[0],
"stripe_orange": stripe_masks[1],
"stripe_yellow": stripe_masks[2],
"stripe_green": stripe_masks[3],
"stripe_blue": stripe_masks[4],
"stripe_violet": stripe_masks[5],
# Chevron after
"chev_black": chev_masks[0],
"chev_brown": chev_masks[1],
"chev_lblue": chev_masks[2],
"chev_pink": chev_masks[3],
"chev_white": chev_masks[4],
}
region_names = list(region_defs.keys())
region_sizes = {name: count(region_defs[name]) for name in region_names}
assert sum(region_sizes.values()) == TOTAL, "Masking error: pixel total mismatch."
# ------------------------------
# Selection helpers
# ------------------------------
rng = np.random.default_rng(42) # deterministic shuffles
unused = np.ones(TOTAL, dtype=bool) # color availability tracker
def circular_hue_distance(hval, hcenter):
d = np.abs(hval - hcenter)
return np.minimum(d, 1.0 - d)
def take_best(k: int, score: np.ndarray) -> np.ndarray:
avail_idx = np.nonzero(unused)[0]
if avail_idx.size < k:
raise RuntimeError("Not enough remaining colors to satisfy allocation.")
s_local = score[avail_idx]
pick_local = np.argpartition(s_local, kth=k-1)[:k]
pick_local = pick_local[np.argsort(s_local[pick_local], kind='mergesort')]
chosen = avail_idx[pick_local]
unused[chosen] = False
return chosen
# ------------------------------
# Scoring per region
# ------------------------------
scores = {}
# Stripe targeting — vibrant, stripe-specific hue centers, good V:
scores["stripe_red"] = (circular_hue_distance(h, 0/360) * 2.0 + (1.0 - s) + 0.4 * (0.75 - v) ** 2)
scores["stripe_orange"] = (circular_hue_distance(h, 30/360) * 2.0 + (1.0 - s) + 0.4 * (0.75 - v) ** 2)
scores["stripe_yellow"] = (circular_hue_distance(h, 60/360) * 2.0 + (1.0 - s) + 0.4 * (0.90 - v) ** 2)
scores["stripe_green"] = (circular_hue_distance(h, 120/360) * 2.0 + (1.0 - s) + 0.4 * (0.75 - v) ** 2)
scores["stripe_blue"] = (circular_hue_distance(h, 240/360) * 2.0 + (1.0 - s) + 0.4 * (0.70 - v) ** 2)
scores["stripe_violet"] = (circular_hue_distance(h, 285/360) * 2.0 + (1.0 - s) + 0.4 * (0.70 - v) ** 2)
# Chevron targeting (from remaining colors after stripes):
scores["chev_black"] = v # darker is better
scores["chev_brown"] = (circular_hue_distance(h, 30/360) * 2.0
+ 0.7 * np.abs(s - 0.5)
+ 3.0 * (v - 0.35) ** 2)
scores["chev_lblue"] = (circular_hue_distance(h, 200/360) * 2.0
+ 1.2 * (1.0 - s)
+ 0.6 * (1.0 - v))
scores["chev_pink"] = (circular_hue_distance(h, 330/360) * 2.0
+ 1.2 * (1.0 - s)
+ 0.6 * (1.0 - v))
scores["chev_white"] = (1.0 - v) + s # bright and desaturated
# ------------------------------
# Allocate colors to regions
# ------------------------------
region_color_indices = {}
# 1) Allocate to stripes FIRST
for key in ["stripe_red", "stripe_orange", "stripe_yellow",
"stripe_green", "stripe_blue", "stripe_violet"]:
k = region_sizes[key]
idx = take_best(k, scores[key])
region_color_indices[key] = idx
# 2) Allocate to chevron from remaining pool
for key in ["chev_black", "chev_brown", "chev_lblue", "chev_pink", "chev_white"]:
k = region_sizes[key]
idx = take_best(k, scores[key])
region_color_indices[key] = idx
# All colors must be consumed exactly once
assert not np.any(unused), "Not all colors were used exactly once."
# ------------------------------
# Paint the image
# ------------------------------
img = np.empty((H, W, 3), dtype=np.uint8)
# Helper to paint a region with a deterministic shuffle
def paint_region(name, mask):
pix = np.column_stack(np.nonzero(mask)) # (N, 2)
order = np.arange(pix.shape[0])
seed = (abs(hash(name)) % (2**32))
rng_reg = np.random.default_rng(seed)
rng_reg.shuffle(order)
chosen_colors = colors[region_color_indices[name]][order]
img[pix[:, 0], pix[:, 1]] = chosen_colors
# Stripes underlay
for k in ["stripe_red", "stripe_orange", "stripe_yellow",
"stripe_green", "stripe_blue", "stripe_violet"]:
paint_region(k, region_defs[k])
# Chevron overlay
for k in ["chev_black", "chev_brown", "chev_lblue", "chev_pink", "chev_white"]:
paint_region(k, region_defs[k])
# ------------------------------
# Final verifications
# ------------------------------
flat = img.reshape(-1, 3)
packed = (flat[:, 0].astype(np.uint32) << 16) | (flat[:, 1].astype(np.uint32) << 8) | flat[:, 2].astype(np.uint32)
uniq = np.unique(packed)
assert uniq.size == TOTAL, f"Color uniqueness failed: {uniq.size} != {TOTAL}"
# Optional (heavy) full-set check:
# all24 = (r.astype(np.uint32) << 16) | (g.astype(np.uint32) << 8) | b.astype(np.uint32)
# assert np.array_equal(np.sort(all24), uniq), "Used colors are not the full 24-bit set."
# ------------------------------
# Save with timestamp
# ------------------------------
ts = datetime.now().strftime("%Y%m%d-%H%M%S")
filename = f"progress_pride_allrgb_8192x2048_{ts}.png"
Image.fromarray(img, mode="RGB").save(filename, compress_level=0)
print(f"Done: {filename}")