Files
WolfensteinVB/graphics/preview_vb_colors.py
2026-02-19 23:28:57 +01:00

103 lines
2.3 KiB
Python

"""
preview_vb_colors.py
Convert images to the Virtual Boy 4-level monochrome red palette and save
preview PNGs so you can see how they look on the VB display.
Palette:
0 = ( 0, 0, 0) -- black / transparent
1 = ( 80, 0, 0) -- dark red
2 = (170, 0, 0) -- medium red
3 = (255, 0, 0) -- bright red
"""
import os
import sys
from PIL import Image
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
VB_PALETTE = {
0: (0, 0, 0),
1: (80, 0, 0),
2: (170, 0, 0),
3: (255, 0, 0),
}
# Bayer 4x4 ordered dither matrix (normalized 0-1)
BAYER_4x4 = [
[0/16, 8/16, 2/16, 10/16],
[12/16, 4/16, 14/16, 6/16],
[3/16, 11/16, 1/16, 9/16],
[15/16, 7/16, 13/16, 5/16],
]
DITHER_STRENGTH = 40 # tweak for more or less dithering
def luminance(r, g, b):
return 0.299 * r + 0.587 * g + 0.114 * b
def convert_to_vb(img):
"""Convert an RGBA/RGB image to VB 4-level red palette with dithering."""
img = img.convert('RGBA')
w, h = img.size
pix = img.load()
out = Image.new('RGB', (w, h), (0, 0, 0))
opix = out.load()
for y in range(h):
for x in range(w):
r, g, b, a = pix[x, y]
if a < 128:
opix[x, y] = VB_PALETTE[0]
continue
lum = luminance(r, g, b)
bayer = BAYER_4x4[y % 4][x % 4]
dithered = lum + (bayer - 0.5) * DITHER_STRENGTH
dithered = max(0, min(255, dithered))
if dithered < 42:
vb = 0
elif dithered < 110:
vb = 1
elif dithered < 180:
vb = 2
else:
vb = 3
opix[x, y] = VB_PALETTE[vb]
return out
def main():
images = [
"wolf_titlescreen.png",
"wolfen_doom_logo.png",
]
for name in images:
path = os.path.join(SCRIPT_DIR, name)
if not os.path.exists(path):
print(f" SKIP: {path} not found")
continue
img = Image.open(path)
print(f"Converting {name} ({img.width}x{img.height})...")
vb_img = convert_to_vb(img)
out_name = os.path.splitext(name)[0] + "_vb_preview.png"
out_path = os.path.join(SCRIPT_DIR, out_name)
vb_img.save(out_path)
print(f" Saved: {out_path}")
print("\nDone!")
if __name__ == '__main__':
main()