import numpy as np
from PIL import Image, ImageDraw, ImageFont
import colorsys
import os
# --- CONFIG ---
IMAGE_SIZE = 4096
BLOCK_SIZE = 1024
NUM_COLORS = 256**3
BLOCKS_PER_SIDE = 4
FONT_SIZE = 48
LINE_SPACING = 8
LABEL_MARGIN = 16
# --- Load bold and italic fonts ---
def load_fonts():
bold_candidates = [
"/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf",
"/System/Library/Fonts/Supplemental/Arial Bold.ttf",
"C:/Windows/Fonts/arialbd.ttf",
"./DejaVuSans-Bold.ttf",
]
italic_candidates = [
"/usr/share/fonts/truetype/dejavu/DejaVuSans-Oblique.ttf",
"/System/Library/Fonts/Supplemental/Arial Italic.ttf",
"C:/Windows/Fonts/ariali.ttf",
"./DejaVuSans-Oblique.ttf",
]
def try_paths(paths):
for path in paths:
if os.path.exists(path):
return ImageFont.truetype(path, FONT_SIZE)
return ImageFont.load_default()
return try_paths(bold_candidates), try_paths(italic_candidates)
# --- Labels ---
animal_labels = [
("Tiger", "Panthera tigris"),
("Zebra", "Equus quagga"),
("Giraffe", "Giraffa camelopardalis"),
("Leopard", "Panthera pardus"),
("Cheetah", "Acinonyx jubatus"),
("Jaguar", "Panthera onca"),
("Okapi", "Okapia johnstoni"),
("Lemur", "Lemur catta"),
("Boa", "Boa constrictor"),
("Python", "Python regius"),
("Monitor", "Varanus niloticus"),
("Flounder", "Bothus lunatus"),
("Clownfish", "Amphiprion ocellaris"),
("Mandarinfish", "Synchiropus splendidus"),
("Dart Frog", "Dendrobates tinctorius"),
("Boxfish", "Ostracion cubicus"),
]
# --- Color generation ---
def generate_rgb_colors():
r = np.arange(256, dtype=np.uint8)
g = np.arange(256, dtype=np.uint8)
b = np.arange(256, dtype=np.uint8)
return np.array(np.meshgrid(r, g, b)).T.reshape(-1, 3)
def sort_by_brightness(colors):
brightness = np.dot(colors, [0.2126, 0.7152, 0.0722])
return colors[np.argsort(brightness)]
def block_coords(index):
row = index // BLOCKS_PER_SIDE
col = index % BLOCKS_PER_SIDE
return row * BLOCK_SIZE, col * BLOCK_SIZE
def safe_tile(base, tile_size, h, w):
tiled = np.kron(base, np.ones((tile_size, tile_size)))
return tiled[:h, :w]
# --- Pattern functions ---
def tiger_pattern(h, w): y, x = np.meshgrid(np.linspace(0, 4*np.pi, h), np.linspace(0, 2*np.pi, w), indexing='ij'); return (0.5 * (np.sin(5*x + np.sin(3*y)) + 1)) ** 3
def zebra_pattern(h, w): x = np.linspace(0, 20*np.pi, w); stripes = np.sin(x); return np.tile((stripes + 1) / 2, (h, 1))
def giraffe_pattern(h, w): y, x = np.indices((h, w)); return (((x//80 + y//80) % 2) * 0.5 + 0.25)
def leopard_pattern(h, w): np.random.seed(1); base = np.random.rand(h//40 + 1, w//40 + 1); return safe_tile(base, 40, h, w)
def cheetah_pattern(h, w): np.random.seed(2); base = np.random.rand(h//20 + 1, w//20 + 1); return safe_tile(base, 20, h, w)
def jaguar_pattern(h, w): y, x = np.ogrid[:h, :w]; return ((np.sin(x/40) * np.cos(y/40)) ** 2)
def okapi_pattern(h, w): x = np.linspace(0, 10*np.pi, w); stripes = (np.sin(x)**8); return np.tile(stripes, (h, 1))
def lemur_pattern(h, w): y = np.linspace(0, 6*np.pi, h); rings = (np.sin(y)**6); return np.tile(rings[:, None], (1, w))
def boa_pattern(h, w): y, x = np.ogrid[:h, :w]; return ((np.sin(x/50) + np.cos(y/50))**2) % 1
def python_pattern(h, w): y, x = np.ogrid[:h, :w]; return (((np.sin(x/30) * np.cos(y/30)) + 1) / 2)
def nile_monitor_pattern(h, w): y, x = np.indices((h, w)); return (((x//40 + y//40) % 3) / 2.0)
def flounder_pattern(h, w): y, x = np.indices((h, w)); return ((np.sin(x/60) * np.sin(y/60))**2)
def clownfish_pattern(h, w): y = np.linspace(0, 4*np.pi, h); stripes = ((np.sin(y)**6) * 0.7 + 0.3); return np.tile(stripes[:, None], (1, w))
def mandarinfish_pattern(h, w): y, x = np.ogrid[:h, :w]; return (np.sin(x/20) + np.sin(y/30)) % 1
def dartfrog_pattern(h, w): y, x = np.indices((h, w)); return (np.sin(np.hypot(x - w//2, y - h//2)/20)**2)
def boxfish_pattern(h, w): y, x = np.indices((h, w)); return (((x//30 + y//30) % 2) * 0.5 + 0.25)
pattern_functions = [
tiger_pattern, zebra_pattern, giraffe_pattern, leopard_pattern,
cheetah_pattern, jaguar_pattern, okapi_pattern, lemur_pattern,
boa_pattern, python_pattern, nile_monitor_pattern, flounder_pattern,
clownfish_pattern, mandarinfish_pattern, dartfrog_pattern, boxfish_pattern
]
# --- Label mask (2 lines per block) ---
def render_labels_and_reserve_coords(font_bold, font_italic):
img = Image.new("L", (IMAGE_SIZE, IMAGE_SIZE), 0)
draw = ImageDraw.Draw(img)
for i, (common, latin) in enumerate(animal_labels):
row, col = block_coords(i)
x = col + LABEL_MARGIN
y = row + LABEL_MARGIN
draw.text((x, y), common, fill=255, font=font_bold)
draw.text((x, y + FONT_SIZE + LINE_SPACING), latin, fill=255, font=font_italic)
return np.array(img) > 0
# --- MAIN ---
def generate_image_with_labels():
font_bold, font_italic = load_fonts()
all_colors = generate_rgb_colors()
sorted_colors = sort_by_brightness(all_colors)
label_mask = render_labels_and_reserve_coords(font_bold, font_italic)
label_indices = np.where(label_mask.flatten())[0]
image_indices = np.where(~label_mask.flatten())[0]
assert len(label_indices) + len(image_indices) == NUM_COLORS
canvas = np.zeros((IMAGE_SIZE * IMAGE_SIZE, 3), dtype=np.uint8)
canvas[image_indices] = sorted_colors[len(label_indices):]
canvas[label_indices] = sorted_colors[:len(label_indices)]
for i in range(16):
print(f"Rendering block {i + 1}/16...")
row, col = block_coords(i)
pattern = pattern_functions[i](BLOCK_SIZE, BLOCK_SIZE)
block_mask = label_mask[row:row + BLOCK_SIZE, col:col + BLOCK_SIZE]
paintable = ~block_mask.flatten()
y_indices = np.arange(BLOCK_SIZE)[:, None] + row
x_indices = np.arange(BLOCK_SIZE)[None, :] + col
yy, xx = np.meshgrid(y_indices, x_indices, indexing='ij')
flat_coords = yy.flatten() * IMAGE_SIZE + xx.flatten()
pattern_vals = pattern.flatten()[paintable]
color_chunk = canvas[flat_coords[paintable]]
sorted_idx = np.argsort(pattern_vals)
canvas[flat_coords[paintable]] = color_chunk[np.argsort(sorted_idx)]
return canvas.reshape((IMAGE_SIZE, IMAGE_SIZE, 3))
# --- EXECUTE ---
if __name__ == "__main__":
img = generate_image_with_labels()
Image.fromarray(img).save("rgb_animal_pattern_grid_with_latin_labels.png")