initial commit!

This commit is contained in:
2026-02-19 23:28:57 +01:00
parent b0d594a9c0
commit 2a36117c25
1558 changed files with 74163 additions and 0 deletions

211
graphics/prepare_win_gfx.py Normal file
View 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()