Skip to content

Instantly share code, notes, and snippets.

@tux-peng
Created April 2, 2026 00:41
Show Gist options
  • Select an option

  • Save tux-peng/d00f51bf9f0cf1051eb0a9405e7c125a to your computer and use it in GitHub Desktop.

Select an option

Save tux-peng/d00f51bf9f0cf1051eb0a9405e7c125a to your computer and use it in GitHub Desktop.
crayola classicube
from PIL import Image, ImageDraw, ImageFont
# The 64 Crayola colors mapped to their standard hex values
CRAYOLA_COLORS = {
"Apricot": "#FDD9B5", "Asparagus": "#87A96B", "Bittersweet": "#FD7C6E", "Black": "#000000",
"Blizzard Blue": "#ACE5EE", "Blue": "#1F75FE", "Blue Green": "#0D98BA", "Blue Violet": "#7366BD",
"Brick Red": "#CB4154", "Brown": "#B4674D", "Burnt Sienna": "#EA7E5D", "Cadet Blue": "#B0B7C6",
"Carnation Pink": "#FFAACC", "Cerulean": "#1DACD6", "Denim": "#2B6CC4", "Forest Green": "#6DAE81",
"Fuchsia": "#C364C5", "Gold": "#E7C697", "Goldenrod": "#FCD975", "Granny Smith Apple": "#A8E4A0",
"Gray": "#95918C", "Green": "#1CAC78", "Green Yellow": "#F0E891", "Hot Magenta": "#FF1DCE",
"Laser Lemon": "#FEFE22", "Macaroni And Cheese": "#FFBD88", "Magenta": "#F664AF", "Mahogany": "#CD4A4C",
"Mauvelous": "#EF98AA", "Melon": "#FDBCB4", "Midnight Blue": "#1A4876", "Olive Green": "#BAB86C",
"Orange": "#FF7538", "Orchid": "#E6A8D7", "Outrageous Orange": "#FF6E4A", "Periwinkle": "#C5D0E6",
"Pine Green": "#158078", "Purple Mountain Majesty": "#9D81BA", "Razzmatazz": "#E3256B", "Red": "#EE204D",
"Red Orange": "#FF5349", "Red Violet": "#C0448F", "Robin’s Egg Blue": "#1FCECB", "Salmon": "#FF9BAA",
"Screamin Green": "#76FF7A", "Sea Green": "#9FE2BF", "Silver": "#CDC5C2", "Sky Blue": "#80DAEB",
"Spring Green": "#ECEABE", "Thistle": "#EBC7DF", "Tickle Me Pink": "#FC89AC", "Timberwolf": "#DBD7D2",
"Tropical Rain Forest": "#17806D", "Tumbleweed": "#DEAA88", "Turquoise Blue": "#77DDE7", "Unmellow Yellow": "#FFFF66",
"Violet (Purple)": "#926EAE", "Violet Red": "#F75394", "White": "#FFFFFF", "Wild Watermelon": "#FD5B78",
"Wisteria": "#CDB4DB", "Yellow": "#FCE883", "Yellow Green": "#C5E384", "Yellow Orange": "#FFAE42"
}
def hex_to_rgb(hex_code):
hex_code = hex_code.lstrip('#')
return tuple(int(hex_code[i:i+2], 16) for i in (0, 2, 4))
def colorize_block(base_image, color_rgb):
"""Multiplies the base texture by the target color."""
colored = Image.new("RGBA", base_image.size)
for x in range(base_image.width):
for y in range(base_image.height):
r, g, b, a = base_image.getpixel((x, y))
# Multiply blend mode
new_r = int((r / 255.0) * color_rgb[0])
new_g = int((g / 255.0) * color_rgb[1])
new_b = int((b / 255.0) * color_rgb[2])
colored.putpixel((x, y), (new_r, new_g, new_b, a))
return colored
def create_text_block(base_image, text):
"""Draws centered text on a base block."""
block = base_image.copy()
draw = ImageDraw.Draw(block)
# Attempt to load a default font, fallback to standard if not found
try:
font = ImageFont.truetype("arial.ttf", 12)
except IOError:
font = ImageFont.load_default()
# Center the text
bbox = draw.textbbox((0, 0), text, font=font)
w = bbox[2] - bbox[0]
h = bbox[3] - bbox[1]
x = (16 - w) / 2
y = (16 - h) / 2 - 1
# Draw black outline then white text for visibility
draw.text((x-1, y), text, font=font, fill="black")
draw.text((x+1, y), text, font=font, fill="black")
draw.text((x, y-1), text, font=font, fill="black")
draw.text((x, y+1), text, font=font, fill="black")
draw.text((x, y), text, font=font, fill="white")
return block
def is_slot_empty(img, tx, ty):
"""Checks if a 16x16 slot is empty (pure purple background in this terrain.png)."""
# EXPLICIT FIX: Protect the original Magenta Wool at Row 4, Column 11
if tx == 11 and ty == 4:
return False
# Look at the center pixel of the tile to determine if it's the empty purple background
r, g, b, a = img.getpixel((tx * 16 + 8, ty * 16 + 8))
if r > 200 and g < 150 and b > 200:
return True
return False
def main():
# Load original terrain
terrain = Image.open("terrain.png").convert("RGBA")
# White wool is at Column 15, Row 4
white_wool = terrain.crop((240, 64, 256, 80))
# Stone block is at index 1 (Column 1, Row 0) for text backgrounds
stone = terrain.crop((16, 0, 32, 16))
new_blocks = []
# 1. Generate A-Z
for char in "ABCDEFGHIJKLMNOPQRSTUVWXYZ":
new_blocks.append(create_text_block(stone, char))
# 2. Generate 0-9
for char in "0123456789":
new_blocks.append(create_text_block(stone, char))
# 3. Generate 64 Crayola Wools
for color_name, hex_code in CRAYOLA_COLORS.items():
rgb = hex_to_rgb(hex_code)
new_blocks.append(colorize_block(white_wool, rgb))
# 4. Pack into empty slots
block_index = 0
total_new_blocks = len(new_blocks)
# Scan grid (16x16) skipping row 15 (breaking animations)
for ty in range(15):
for tx in range(16):
if block_index >= total_new_blocks:
break
if is_slot_empty(terrain, tx, ty):
# Paste the new block into the empty slot
terrain.paste(new_blocks[block_index], (tx * 16, ty * 16))
block_index += 1
if block_index < total_new_blocks:
print(f"Warning: Ran out of empty slots! Placed {block_index}/{total_new_blocks} blocks.")
else:
print(f"Successfully added all {total_new_blocks} blocks!")
terrain.save("terrain_extended.png")
print("Saved as terrain_extended.png")
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment