240 lines
7.9 KiB
Python
240 lines
7.9 KiB
Python
import os
|
|
from pathlib import Path
|
|
from PIL import Image
|
|
|
|
PROJECT_DIR = Path(__file__).resolve().parents[2]
|
|
BRAIN_DIR = PROJECT_DIR / "assets" / "sprites"
|
|
OUT_DIR = PROJECT_DIR / "assets" / "textures"
|
|
OUT_BIN = OUT_DIR / "casino3d.bin"
|
|
|
|
CHROMAKEY = (255, 0, 255) # Magenta: 0x00FF00FF
|
|
TEX_SIZE = 64
|
|
TOTAL_SLOTS = 48
|
|
|
|
def clean_chromakey_black(img: Image.Image, threshold=40) -> Image.Image:
|
|
"""Resize with NEAREST so pixels never blend with chromakey!"""
|
|
img = img.resize((TEX_SIZE, TEX_SIZE), Image.Resampling.NEAREST).convert("RGB")
|
|
pixels = img.load()
|
|
for y in range(TEX_SIZE):
|
|
for x in range(TEX_SIZE):
|
|
r, g, b = pixels[x, y]
|
|
if r < threshold and g < threshold and b < threshold:
|
|
pixels[x, y] = CHROMAKEY
|
|
return img
|
|
|
|
def clean_chromakey_white(img: Image.Image, threshold=220) -> Image.Image:
|
|
img = img.resize((TEX_SIZE, TEX_SIZE), Image.Resampling.NEAREST).convert("RGB")
|
|
pixels = img.load()
|
|
for y in range(TEX_SIZE):
|
|
for x in range(TEX_SIZE):
|
|
r, g, b = pixels[x, y]
|
|
if r > threshold and g > threshold and b > threshold:
|
|
pixels[x, y] = CHROMAKEY
|
|
return img
|
|
|
|
def img_to_raw_dwords(img: Image.Image) -> bytes:
|
|
img = img.resize((TEX_SIZE, TEX_SIZE), Image.Resampling.NEAREST).convert("RGB")
|
|
data = bytearray()
|
|
pixels = img.load()
|
|
for y in range(TEX_SIZE):
|
|
for x in range(TEX_SIZE):
|
|
r, g, b = pixels[x, y]
|
|
# Little-endian 32-bit: [Blue, Green, Red, 0]
|
|
data.extend([b, g, r, 0])
|
|
return bytes(data)
|
|
|
|
def make_wood_wall() -> bytes:
|
|
data = bytearray()
|
|
for y in range(TEX_SIZE):
|
|
for x in range(TEX_SIZE):
|
|
plank = (x // 16)
|
|
grain = (y * 3 + x * 7) % 13
|
|
r = min(255, 120 + plank * 15 + grain)
|
|
g = min(255, 60 + plank * 8 + (grain // 2))
|
|
b = min(255, 25 + plank * 4)
|
|
if x % 16 == 0 or y == 0 or y == 63:
|
|
r, g, b = 45, 20, 10
|
|
data.extend([b, g, r, 0])
|
|
return bytes(data)
|
|
|
|
def make_carpet_floor() -> bytes:
|
|
data = bytearray()
|
|
for y in range(TEX_SIZE):
|
|
for x in range(TEX_SIZE):
|
|
# Rich emerald green casino carpet with gold diamond pattern
|
|
cx = (x % 16) - 8
|
|
cy = (y % 16) - 8
|
|
d = abs(cx) + abs(cy)
|
|
if d == 6 or d == 7:
|
|
# Gold line
|
|
r, g, b = 212, 175, 55
|
|
elif d < 6:
|
|
# Dark emerald center
|
|
r, g, b = 15, 75, 35
|
|
else:
|
|
# Deep velvet green
|
|
r, g, b = 20, 110, 45
|
|
data.extend([b, g, r, 0])
|
|
return bytes(data)
|
|
|
|
def make_ceiling_tex() -> bytes:
|
|
data = bytearray()
|
|
for y in range(TEX_SIZE):
|
|
for x in range(TEX_SIZE):
|
|
# Dark navy casino ceiling with warm gold recessed lamps
|
|
dx = (x % 32) - 16
|
|
dy = (y % 32) - 16
|
|
dist2 = dx * dx + dy * dy
|
|
if dist2 < 16:
|
|
# Bright warm lamp
|
|
r, g, b = 255, 240, 180
|
|
elif dist2 < 36:
|
|
# Gold bezel
|
|
r, g, b = 180, 140, 40
|
|
else:
|
|
# Dark acoustic tile
|
|
grid = 15 if (x % 32 == 0 or y % 32 == 0) else 35
|
|
r, g, b = grid, grid, grid + 10
|
|
data.extend([b, g, r, 0])
|
|
return bytes(data)
|
|
|
|
def make_atm() -> bytes:
|
|
data = bytearray()
|
|
for y in range(TEX_SIZE):
|
|
for x in range(TEX_SIZE):
|
|
if x < 14 or x > 49 or y < 6:
|
|
r, g, b = CHROMAKEY
|
|
elif 10 <= y <= 22 and 18 <= x <= 45:
|
|
# Glowing green screen
|
|
r, g, b = 20, 220, 40
|
|
elif 26 <= y <= 30 and 20 <= x <= 43:
|
|
r, g, b = 10, 10, 10
|
|
elif 42 <= y <= 54 and 18 <= x <= 45:
|
|
# Gold dispenser
|
|
r, g, b = 220, 180, 40
|
|
else:
|
|
r, g, b = 70, 75, 85
|
|
data.extend([b, g, r, 0])
|
|
return bytes(data)
|
|
|
|
def make_backroom_table() -> bytes:
|
|
data = bytearray()
|
|
for y in range(TEX_SIZE):
|
|
for x in range(TEX_SIZE):
|
|
if x < 8 or x > 55 or y < 16:
|
|
r, g, b = CHROMAKEY
|
|
elif y < 38:
|
|
r, g, b = 80, 40, 20
|
|
if 22 <= x <= 36 and 22 <= y <= 28:
|
|
r, g, b = 160, 160, 170 # Gun
|
|
elif (x - 44)**2 + (y - 28)**2 < 9:
|
|
r, g, b = 180, 10, 10 # Blood
|
|
else:
|
|
if (12 <= x <= 16 or 47 <= x <= 51):
|
|
r, g, b = 45, 20, 10
|
|
else:
|
|
r, g, b = CHROMAKEY
|
|
data.extend([b, g, r, 0])
|
|
return bytes(data)
|
|
|
|
def main():
|
|
OUT_DIR.mkdir(parents=True, exist_ok=True)
|
|
atlas = {}
|
|
|
|
# 1: Velvet Wall
|
|
velvet_path = BRAIN_DIR / "casino_wall_velvet_1790125679687.jpg"
|
|
if velvet_path.exists():
|
|
im = Image.open(velvet_path)
|
|
atlas[1] = img_to_raw_dwords(im)
|
|
else:
|
|
atlas[1] = make_wood_wall()
|
|
|
|
# 2: Neon Strip Club Wall
|
|
neon_path = BRAIN_DIR / "strip_club_wall_1790125693490.jpg"
|
|
if neon_path.exists():
|
|
im = Image.open(neon_path)
|
|
atlas[2] = img_to_raw_dwords(im)
|
|
else:
|
|
atlas[2] = make_wood_wall()
|
|
|
|
# 3: Carpet Floor
|
|
carpet_path = BRAIN_DIR / "casino_carpet_floor_1790125783306.jpg"
|
|
if carpet_path.exists():
|
|
im = Image.open(carpet_path)
|
|
atlas[3] = img_to_raw_dwords(im)
|
|
else:
|
|
atlas[3] = make_carpet_floor()
|
|
|
|
# 4: Hell Wall
|
|
hell_path = BRAIN_DIR / "doom_hell_wall_1790125800167.jpg"
|
|
if hell_path.exists():
|
|
im = Image.open(hell_path)
|
|
atlas[4] = img_to_raw_dwords(im)
|
|
else:
|
|
atlas[4] = make_wood_wall()
|
|
|
|
# 5: Ceiling
|
|
atlas[5] = make_ceiling_tex()
|
|
|
|
# 6: Wood Wall
|
|
atlas[6] = make_wood_wall()
|
|
|
|
# 10..13: Stripper Dancers (Clean crop with NEAREST)
|
|
dancer_path = BRAIN_DIR / "stripper_dancer_sprite_1790125706706.jpg"
|
|
if dancer_path.exists():
|
|
sheet = Image.open(dancer_path).convert("RGB")
|
|
w, h = sheet.size
|
|
fw = w // 4
|
|
for i in range(4):
|
|
crop = sheet.crop((i * fw, 0, (i + 1) * fw, h))
|
|
crop_clean = clean_chromakey_black(crop, threshold=40)
|
|
atlas[10 + i] = img_to_raw_dwords(crop_clean)
|
|
|
|
# 20..22: Slot Machine Frames
|
|
slot_path = BRAIN_DIR / "slot_machine_sprite_1790125722742.jpg"
|
|
if slot_path.exists():
|
|
sheet = Image.open(slot_path).convert("RGB")
|
|
w, h = sheet.size
|
|
fw = w // 3
|
|
for i in range(3):
|
|
crop = sheet.crop((i * fw, 0, (i + 1) * fw, h))
|
|
crop_clean = clean_chromakey_black(crop, threshold=35)
|
|
atlas[20 + i] = img_to_raw_dwords(crop_clean)
|
|
|
|
# 30: Blackjack Table
|
|
bj_path = BRAIN_DIR / "blackjack_table_sprite_1790125738107.jpg"
|
|
if bj_path.exists():
|
|
im = Image.open(bj_path)
|
|
atlas[30] = img_to_raw_dwords(clean_chromakey_white(im, threshold=225))
|
|
|
|
# 31: Roulette Table
|
|
roul_path = BRAIN_DIR / "roulette_table_sprite_1790125754084.jpg"
|
|
if roul_path.exists():
|
|
im = Image.open(roul_path)
|
|
atlas[31] = img_to_raw_dwords(clean_chromakey_black(im, threshold=35))
|
|
|
|
# 32: Bartender
|
|
shop_path = BRAIN_DIR / "bartender_shop_sprite_1790125768774.jpg"
|
|
if shop_path.exists():
|
|
im = Image.open(shop_path)
|
|
atlas[32] = img_to_raw_dwords(clean_chromakey_white(im, threshold=225))
|
|
|
|
# 33: ATM
|
|
atlas[33] = make_atm()
|
|
|
|
# 34: Backroom Table
|
|
atlas[34] = make_backroom_table()
|
|
|
|
# Monolithic binary write
|
|
slot_len = TEX_SIZE * TEX_SIZE * 4
|
|
with open(OUT_BIN, "wb") as f:
|
|
for slot in range(TOTAL_SLOTS):
|
|
data = atlas.get(slot, atlas.get(1, make_wood_wall()))
|
|
if len(data) != slot_len:
|
|
data = data.ljust(slot_len, b'\x00')[:slot_len]
|
|
f.write(data)
|
|
|
|
print(f"Generated clean casino3d.bin ({OUT_BIN.stat().st_size} bytes)")
|
|
|
|
if __name__ == "__main__":
|
|
main()
|