added tools

This commit is contained in:
Chipperfluff 2025-12-18 23:45:19 +01:00
parent b0eb7e0751
commit 98be4b5aee
3 changed files with 227 additions and 0 deletions

64
tools/recolor/__init__.py Normal file
View File

@ -0,0 +1,64 @@
from dataclasses import dataclass
from PIL import Image
import importlib
@dataclass(frozen=True)
class Color:
RED: int
GREEN: int
BLUE: int
@dataclass(frozen=True)
class ColorMask:
target_color: Color
replacement_color: Color
def load_color_map(name: str):
module_path = f"tools.recolor.color_maps.{name}"
module = importlib.import_module(module_path)
if not hasattr(module, "COLOR_MAP"):
raise RuntimeError(f"color map '{name}' has no COLOR_MAP")
return module.COLOR_MAP
def recolor_image(img: Image.Image, color_map: list[ColorMask]) -> Image.Image:
img = img.convert("RGBA")
pixels = img.load()
lookup = {
(m.target_color.RED, m.target_color.GREEN, m.target_color.BLUE):
(m.replacement_color.RED, m.replacement_color.GREEN, m.replacement_color.BLUE)
for m in color_map
}
width, height = img.size
for y in range(height):
for x in range(width):
r, g, b, a = pixels[x, y]
if (r, g, b) in lookup:
nr, ng, nb = lookup[(r, g, b)]
pixels[x, y] = (nr, ng, nb, a)
return img
def extract_unique_colors(img: Image.Image) -> set[Color]:
img = img.convert("RGBA")
pixels = img.load()
width, height = img.size
colors: set[Color] = set()
for y in range(height):
for x in range(width):
r, g, b, a = pixels[x, y]
if a != 0:
colors.add(Color(r, g, b))
return colors

128
tools/recolor/__main__.py Normal file
View File

@ -0,0 +1,128 @@
import argparse
import sys
from pathlib import Path
from PIL import Image
from . import load_color_map, recolor_image, extract_unique_colors
def collect_files(dir_arg: str) -> list[Path]:
path = Path(dir_arg)
if "*" in dir_arg:
parent = path.parent
pattern = path.name
if not parent.exists() or not parent.is_dir():
print("directory does not exist", file=sys.stderr)
sys.exit(1)
return [
f for f in parent.iterdir()
if f.is_file()
and f.suffix.lower() == ".png"
and f.match(pattern)
]
if not path.exists() or not path.is_dir():
print("directory does not exist", file=sys.stderr)
sys.exit(1)
return [
f for f in path.iterdir()
if f.is_file() and f.suffix.lower() == ".png"
]
def process_file(path: Path, color_map):
img = Image.open(path)
img = recolor_image(img, color_map)
img.save(path)
def main():
parser = argparse.ArgumentParser()
parser.add_argument("-i", "--input")
parser.add_argument("-dir", "--directory")
parser.add_argument("-o", "--output")
parser.add_argument("-r", "--replace", action="store_true")
parser.add_argument("-m", "--map")
parser.add_argument("-ext", "--extract", action="store_true")
args = parser.parse_args()
if not args.input and not args.directory:
print("either --input or --directory is required", file=sys.stderr)
sys.exit(1)
# -------- directory / glob mode --------
if args.directory:
files = collect_files(args.directory)
if args.extract:
all_colors = set()
for file in files:
img = Image.open(file)
all_colors |= extract_unique_colors(img)
for c in sorted(all_colors, key=lambda x: (x.RED, x.GREEN, x.BLUE)):
print(f"{c.RED:3}, {c.GREEN:3}, {c.BLUE:3}")
sys.exit(0)
if not args.map:
print("color map required for directory mode", file=sys.stderr)
sys.exit(1)
try:
color_map = load_color_map(args.map)
except Exception as e:
print(str(e), file=sys.stderr)
sys.exit(1)
for file in files:
process_file(file, color_map)
sys.exit(0)
# -------- single file mode --------
input_path = Path(args.input)
if not input_path.exists():
print("input file does not exist", file=sys.stderr)
sys.exit(1)
if args.extract:
img = Image.open(input_path)
colors = extract_unique_colors(img)
for c in sorted(colors, key=lambda x: (x.RED, x.GREEN, x.BLUE)):
print(f"{c.RED:3}, {c.GREEN:3}, {c.BLUE:3}")
sys.exit(0)
if args.replace:
output_path = input_path
else:
if not args.output:
print("output file required unless --replace is set", file=sys.stderr)
sys.exit(1)
output_path = Path(args.output)
if not args.map:
print("color map required unless --extract is set", file=sys.stderr)
sys.exit(1)
try:
color_map = load_color_map(args.map)
except Exception as e:
print(str(e), file=sys.stderr)
sys.exit(1)
img = Image.open(input_path)
img = recolor_image(img, color_map)
img.save(output_path)
if __name__ == "__main__":
main()

View File

@ -0,0 +1,35 @@
from tools.recolor import Color, ColorMask
COLOR_MAP = [
# ---- item/armor (teal -> orange) ----
ColorMask(Color( 8, 37, 32), Color(100, 45, 10)),
ColorMask(Color( 14, 62, 53), Color(130, 60, 15)),
ColorMask(Color( 26, 168, 165), Color(160, 80, 20)),
ColorMask(Color( 32, 195, 179), Color(190, 100, 25)),
ColorMask(Color( 73, 234, 214), Color(220, 120, 30)),
ColorMask(Color(159, 248, 229), Color(235, 140, 40)),
ColorMask(Color(252, 252, 252), Color(255, 220, 180)),
# ---- model/armor (old teal -> orange) ----
ColorMask(Color( 44, 224, 216), Color(130, 60, 15)),
ColorMask(Color( 45, 196, 178), Color(160, 80, 20)),
ColorMask(Color( 48, 208, 190), Color(190, 100, 25)),
ColorMask(Color( 74, 237, 217), Color(220, 120, 30)),
ColorMask(Color(107, 243, 227), Color(235, 140, 40)),
ColorMask(Color(154, 248, 240), Color(245, 160, 60)),
ColorMask(Color(161, 251, 232), Color(255, 180, 100)),
ColorMask(Color(180, 253, 238), Color(255, 200, 140)),
ColorMask(Color(229, 255, 250), Color(255, 220, 180)),
# ---- already orange (identity, makes it safe to rerun) ----
ColorMask(Color(100, 45, 10), Color(100, 45, 10)),
ColorMask(Color(130, 60, 15), Color(130, 60, 15)),
ColorMask(Color(160, 80, 20), Color(160, 80, 20)),
ColorMask(Color(190, 100, 25), Color(190, 100, 25)),
ColorMask(Color(220, 120, 30), Color(220, 120, 30)),
ColorMask(Color(235, 140, 40), Color(235, 140, 40)),
ColorMask(Color(245, 160, 60), Color(245, 160, 60)),
ColorMask(Color(255, 180, 100), Color(255, 180, 100)),
ColorMask(Color(255, 200, 140), Color(255, 200, 140)),
ColorMask(Color(255, 220, 180), Color(255, 220, 180)),
]