xpet-fork/toxpm.py
2025-11-04 14:59:39 -05:00

81 lines
1.9 KiB
Python

"""
this is a minimal jpg/png->xpm converter
so that you can make your pets with almost
any program and convert it to xpm later
"""
from PIL import Image
from typing import Any
import sys
import os
import re
def symbol_from_output(path: str) -> str:
base = os.path.splitext(os.path.basename(path))[0]
# e.g., idle_0 -> xpm_idle_0
clean = re.sub(r'[^a-zA-Z0-9_]', '_', base)
if not clean:
clean = "img"
return f"xpm_{clean}"
def toxpm(input_path):
output_path = sys.argv[2] if len(sys.argv) >= 3 else "img.xpm"
symbol = symbol_from_output(output_path)
image = Image.open(input_path).convert("RGBA")
image = image.quantize(256).convert("RGBA") # xpm needs 256colours
width = image.size[0]
height = image.size[1]
pixels: Any = image.load()
colors = {}
next_char = 33 # start printable ascii
def next_symbol():
nonlocal next_char
while True:
char = chr(next_char)
next_char += 1
if char in ['"', "'", '\\']:
continue
break
return char
pixmap = []
for y in range(height):
row = []
for x in range(width):
rgba = pixels[x, y]
if rgba[3] < 128:
rgba = (0, 0, 0, 0)
if rgba not in colors:
colors[rgba] = next_symbol()
row.append(colors[rgba])
pixmap.append("".join(row))
with open(output_path, "w") as f:
# writing c
f.write("/* xpm */\n")
f.write(f"static const char* {symbol}[] = {{\n")
f.write(f"\"{width} {height} {len(colors)} 1\",\n")
for rgba, sym in colors.items():
if rgba[3] == 0:
f.write(f"\"{sym} c None\",\n")
else:
f.write(f"\"{sym} c #{rgba[0]:02x}{rgba[1]:02x}{rgba[2]:02x}\",\n")
for y, row in enumerate(pixmap):
line_end = "," if y < height - 1 else ""
f.write(f"\"{row}\"{line_end}\n")
f.write("};\n")
print(f"saved xpm: {output_path} (symbol: {symbol})")
if len(sys.argv) < 2:
print("usage: toxpm.py <input.(jpg/png)> [output.xpm]")
sys.exit(1)
toxpm(sys.argv[1])