initial commit!
This commit is contained in:
211
graphics/prepare_win_gfx.py
Normal file
211
graphics/prepare_win_gfx.py
Normal file
@@ -0,0 +1,211 @@
|
||||
"""
|
||||
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()
|
||||
Reference in New Issue
Block a user