212 lines
6.6 KiB
Python
212 lines
6.6 KiB
Python
"""
|
|
prepare_win_gfx.py
|
|
|
|
Convert Doom intermission label PNGs (win_gfx/) to VB 2bpp tile + map arrays.
|
|
Each image is 12px tall (2 tile rows, padded), variable width.
|
|
|
|
Output: src/vbdoom/assets/images/win_gfx.c and win_gfx.h
|
|
"""
|
|
|
|
import os
|
|
from PIL import Image
|
|
|
|
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
INPUT_DIR = os.path.join(SCRIPT_DIR, "win_gfx")
|
|
OUTPUT_C = os.path.join(SCRIPT_DIR, "src", "vbdoom", "assets", "images", "win_gfx.c")
|
|
OUTPUT_H = os.path.join(SCRIPT_DIR, "src", "vbdoom", "assets", "images", "win_gfx.h")
|
|
|
|
TILE_W = 8
|
|
TILE_H = 8
|
|
|
|
# Images to convert: (filename, C prefix)
|
|
LABELS = [
|
|
("WITIME.png", "witime"),
|
|
("WISCRT2.png", "wiscrt"),
|
|
("WIOSTK.png", "wiostk"),
|
|
("WIOSTI.png", "wiosti"),
|
|
("WIENTER.png", "wienter"),
|
|
("WIF.png", "wif"),
|
|
("WIFRGS.png", "wifrgs"),
|
|
("WIMSTT.png", "wimstt"),
|
|
("WIPCNT.png", "wipcnt"),
|
|
]
|
|
|
|
|
|
def build_palette_remap(img):
|
|
"""Map PNG palette indices to VB brightness levels (0-3)."""
|
|
pal = img.getpalette()
|
|
if pal is None:
|
|
# Not indexed -- convert from RGB
|
|
return None
|
|
|
|
used_indices = sorted(set(img.getdata()))
|
|
idx_brightness = []
|
|
for idx in used_indices:
|
|
r = pal[idx * 3]
|
|
idx_brightness.append((idx, r))
|
|
|
|
idx_brightness.sort(key=lambda x: x[1])
|
|
remap = {}
|
|
for vb_level, (png_idx, _) in enumerate(idx_brightness):
|
|
if vb_level > 3:
|
|
vb_level = 3
|
|
remap[png_idx] = vb_level
|
|
|
|
return remap
|
|
|
|
|
|
def pixel_to_vb(img, px, py, remap):
|
|
"""Get VB brightness for pixel at (px, py)."""
|
|
if px >= img.width or py >= img.height:
|
|
return 0 # padding
|
|
val = img.getpixel((px, py))
|
|
if remap is not None:
|
|
return remap.get(val, 0)
|
|
# RGB mode
|
|
if isinstance(val, tuple):
|
|
r = val[0]
|
|
if r < 32:
|
|
return 0
|
|
elif r < 96:
|
|
return 1
|
|
elif r < 192:
|
|
return 2
|
|
else:
|
|
return 3
|
|
return val & 3
|
|
|
|
|
|
def encode_tile_words(pixels):
|
|
"""Convert 64-pixel list to 4 VB 2bpp u32 words (sequential format)."""
|
|
words = []
|
|
for pair in range(4):
|
|
row_a = pair * 2
|
|
row_b = pair * 2 + 1
|
|
u16_a = 0
|
|
u16_b = 0
|
|
for x in range(TILE_W):
|
|
u16_a |= (pixels[row_a * TILE_W + x] << (x * 2))
|
|
u16_b |= (pixels[row_b * TILE_W + x] << (x * 2))
|
|
word = u16_a | (u16_b << 16)
|
|
words.append(word)
|
|
return words
|
|
|
|
|
|
def convert_image(filepath, prefix):
|
|
"""Convert one label PNG to tile data + map entries."""
|
|
img = Image.open(filepath)
|
|
w, h = img.size
|
|
|
|
# Pad height to multiple of 8 (tiles are 8x8)
|
|
tile_cols = (w + TILE_W - 1) // TILE_W
|
|
tile_rows = (h + TILE_H - 1) // TILE_H
|
|
|
|
remap = None
|
|
if img.mode == 'P':
|
|
remap = build_palette_remap(img)
|
|
elif img.mode == 'RGBA':
|
|
img = img.convert('RGB')
|
|
|
|
# Build tile bank with dedup
|
|
tile_bank = [] # list of word-tuples
|
|
tile_lookup = {} # pixel_tuple -> index
|
|
map_entries = [] # one u16 per cell
|
|
|
|
# Tile 0 = blank
|
|
blank = tuple([0] * 64)
|
|
blank_words = tuple(encode_tile_words(list(blank)))
|
|
tile_bank.append(blank_words)
|
|
tile_lookup[blank] = 0
|
|
|
|
for ty in range(tile_rows):
|
|
for tx in range(tile_cols):
|
|
pixels = []
|
|
for py in range(TILE_H):
|
|
for px in range(TILE_W):
|
|
pixels.append(pixel_to_vb(img, tx * TILE_W + px, ty * TILE_H + py, remap))
|
|
pixels = tuple(pixels)
|
|
|
|
if pixels in tile_lookup:
|
|
map_entries.append(tile_lookup[pixels])
|
|
else:
|
|
idx = len(tile_bank)
|
|
tile_bank.append(tuple(encode_tile_words(list(pixels))))
|
|
tile_lookup[pixels] = idx
|
|
map_entries.append(idx)
|
|
|
|
print(f" {os.path.basename(filepath)}: {w}x{h} -> {tile_cols}x{tile_rows} tiles, "
|
|
f"{len(tile_bank)} unique, {len(tile_bank)*16} bytes")
|
|
|
|
return tile_bank, map_entries, tile_cols, tile_rows
|
|
|
|
|
|
def main():
|
|
print("Converting win_gfx label images to VB 2bpp tile data...\n")
|
|
|
|
all_results = []
|
|
for fname, prefix in LABELS:
|
|
fpath = os.path.join(INPUT_DIR, fname)
|
|
if not os.path.isfile(fpath):
|
|
print(f" WARNING: {fpath} not found, skipping")
|
|
continue
|
|
result = convert_image(fpath, prefix)
|
|
all_results.append((prefix, result))
|
|
|
|
# Write C file
|
|
with open(OUTPUT_C, 'w') as f:
|
|
f.write("/* win_gfx -- Doom intermission label tiles\n")
|
|
f.write(" * Auto-generated by prepare_win_gfx.py\n */\n\n")
|
|
|
|
for prefix, (tile_bank, map_entries, tcols, trows) in all_results:
|
|
num_words = len(tile_bank) * 4
|
|
all_words = [w for tile in tile_bank for w in tile]
|
|
|
|
f.write(f"/* {prefix}: {tcols}x{trows} tiles, {len(tile_bank)} unique */\n")
|
|
f.write(f"const unsigned int {prefix}Tiles[{num_words}]"
|
|
f" __attribute__((aligned(4))) =\n{{\n")
|
|
for i, word in enumerate(all_words):
|
|
if i % 8 == 0:
|
|
f.write("\t")
|
|
f.write(f"0x{word:08X}")
|
|
if i < num_words - 1:
|
|
f.write(",")
|
|
if i % 8 == 7:
|
|
f.write("\n")
|
|
if num_words % 8 != 0:
|
|
f.write("\n")
|
|
f.write("};\n\n")
|
|
|
|
f.write(f"const unsigned short {prefix}Map[{len(map_entries)}]"
|
|
f" __attribute__((aligned(4))) =\n{{\n")
|
|
for i, entry in enumerate(map_entries):
|
|
if i % 8 == 0:
|
|
f.write("\t")
|
|
f.write(f"0x{entry:04X}")
|
|
if i < len(map_entries) - 1:
|
|
f.write(",")
|
|
if i % 8 == 7:
|
|
f.write("\n")
|
|
if len(map_entries) % 8 != 0:
|
|
f.write("\n")
|
|
f.write("};\n\n")
|
|
|
|
# Write H file
|
|
with open(OUTPUT_H, 'w') as f:
|
|
f.write("#ifndef __WIN_GFX_H__\n#define __WIN_GFX_H__\n\n")
|
|
for prefix, (tile_bank, map_entries, tcols, trows) in all_results:
|
|
num_words = len(tile_bank) * 4
|
|
f.write(f"#define {prefix.upper()}_TILES {len(tile_bank)}\n")
|
|
f.write(f"#define {prefix.upper()}_TILE_BYTES {len(tile_bank)*16}\n")
|
|
f.write(f"#define {prefix.upper()}_COLS {tcols}\n")
|
|
f.write(f"#define {prefix.upper()}_ROWS {trows}\n")
|
|
f.write(f"extern const unsigned int {prefix}Tiles[{num_words}];\n")
|
|
f.write(f"extern const unsigned short {prefix}Map[{len(map_entries)}];\n\n")
|
|
f.write("#endif\n")
|
|
|
|
print(f"\nWritten: {OUTPUT_C}")
|
|
print(f"Written: {OUTPUT_H}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|