added tools
This commit is contained in:
parent
b0eb7e0751
commit
98be4b5aee
64
tools/recolor/__init__.py
Normal file
64
tools/recolor/__init__.py
Normal 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
128
tools/recolor/__main__.py
Normal 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()
|
||||||
35
tools/recolor/color_maps/chipper_orange.py
Normal file
35
tools/recolor/color_maps/chipper_orange.py
Normal 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)),
|
||||||
|
]
|
||||||
Loading…
x
Reference in New Issue
Block a user