Skip to content

Instantly share code, notes, and snippets.

@tux-peng
Created April 11, 2026 01:34
Show Gist options
  • Select an option

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

Select an option

Save tux-peng/09d2f8ea14891f11d12819814a68eeef to your computer and use it in GitHub Desktop.
Add letters, numbers and all 120 colours from crayola's 1996 box to classicube
from PIL import Image, ImageDraw, ImageFont
# The 120 Crayola colors mapped to their standard hex values
CRAYOLA_COLORS = {
# Reds & pinks
"Almond": "#EFDECD", "Apricot": "#FDD9B5", "Bittersweet": "#FD7C6E", "Blush": "#DE5D83",
"Brick Red": "#CB4154", "Carnation Pink": "#FFAACC", "Cerise": "#DD4492", "Chestnut": "#BC5D58",
"Jazzberry Jam": "#CA3767", "Mahogany": "#CD4A4C", "Mango Tango": "#FF8243", "Mauvelous": "#EF98AA",
"Melon": "#FDBCB4", "Peach": "#FFCBA4", "Pink Flamingo": "#FC74FD", "Pink Sherbert": "#F78FA7",
"Radical Red": "#FF496C", "Razzmatazz": "#E3256B", "Red": "#EE204D", "Salmon": "#FF9BAA",
"Scarlet": "#FC2847", "Sunset Orange": "#FD5E53", "Tickle Me Pink": "#FC89AC",
"Violet Red": "#F75394", "Wild Strawberry": "#FF43A4",
# Oranges
"Atomic Tangerine": "#FFA474", "Burnt Orange": "#FF7034", "Burnt Sienna": "#EA7E5D",
"Copper": "#DD9475", "Macaroni And Cheese": "#FFBD88", "Neon Carrot": "#FFA343",
"Orange": "#FF7538", "Outrageous Orange": "#FF6E4A", "Red Orange": "#FF5349",
"Tumbleweed": "#DEAA88", "Vivid Tangerine": "#FF9980",
# Yellows
"Banana Mania": "#FAE7B5", "Canary": "#FFFF99", "Dandelion": "#FED85D",
"Desert Sand": "#EFCDB8", "Gold": "#E7C697", "Goldenrod": "#FCD975",
"Laser Lemon": "#FEFE22", "Sunglow": "#FFCF48", "Tan": "#FAA76C",
"Unmellow Yellow": "#FFFF66", "Yellow": "#FCE883", "Yellow Orange": "#FFAE42",
# Yellow-greens & limes
"Electric Lime": "#CEFF1D", "Green Yellow": "#F0E891", "Inchworm": "#B2EC5D",
"Olive Green": "#BAB86C", "Spring Green": "#ECEABE", "Yellow Green": "#C5E384",
# Greens
"Asparagus": "#87A96B", "Fern": "#71BC78", "Forest Green": "#6DAE81",
"Granny Smith Apple": "#A8E4A0", "Green": "#1CAC78", "Jungle Green": "#3BB08F",
"Mountain Meadow": "#30BA8F", "Pine Green": "#158078", "Screamin Green": "#76FF7A",
"Sea Green": "#9FE2BF", "Shamrock": "#45CEA2", "Tropical Rain Forest": "#17806D",
# Blues & teals
"Aquamarine": "#78DBE2", "Blue": "#1F75FE", "Blue Green": "#0D98BA",
"Caribbean Green": "#1CD3A2", "Cerulean": "#1DACD6", "Cornflower": "#9ACEEB",
"Denim": "#2B6CC4", "Midnight Blue": "#1A4876", "Navy Blue": "#1974D2",
"Pacific Blue": "#1CA9C9", "Robin's Egg Blue": "#1FCECB", "Sky Blue": "#80DAEB",
"Timberwolf": "#DBD7D2", "Turquoise Blue": "#77DDE7",
# Purples & violets
"Blue Bell": "#A2A2D0", "Blue Violet": "#7366BD", "Cadet Blue": "#B0B7C6",
"Eggplant": "#6E5160", "Fuchsia": "#C364C5", "Indigo": "#5D76CB",
"Lavender": "#FCB4D5", "Manatee": "#979AAA", "Mulberry": "#C54B8C",
"Orchid": "#E6A8D7", "Outer Space": "#414A4C", "Periwinkle": "#C5D0E6",
"Plum": "#8E4585", "Purple Heart": "#7442C8", "Purple Mountain Majesty": "#9D81BA",
"Purple Pizzazz": "#FE4EDA", "Razzle Dazzle Rose": "#FF48D0", "Red Violet": "#C0448F",
"Royal Purple": "#7851A9", "Shocking Pink": "#FB7EFD", "Thistle": "#EBC7DF",
"Vivid Violet": "#8F509D", "Violet (Purple)": "#926EAE", "Wild Blue Yonder": "#A2ADD0",
"Wisteria": "#CDB4DB",
# Fluorescents
"Hot Magenta": "#FF1DCE", "Magenta": "#F664AF", "Wild Watermelon": "#FD5B78",
# Neutrals
"Antique Brass": "#CD9575", "Blizzard Blue": "#ACE5EE", "Maroon": "#C8385A",
"Sepia": "#A5694F", "Shadow": "#8A795D",
"Beaver": "#9F8170", "Black": "#000000", "Brown": "#B4674D",
"Fuzzy Wuzzy": "#CC6666", "Gray": "#95918C", "Silver": "#CDC5C2", "White": "#FFFFFF",
}
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