89 lines
3.3 KiB
Python
89 lines
3.3 KiB
Python
#!/usr/bin/env python3
|
|
"""Slice a fixed grid, remove green chroma, and resize with premultiplied alpha."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
from pathlib import Path
|
|
|
|
from PIL import Image
|
|
|
|
|
|
def parse_args() -> argparse.Namespace:
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("image", type=Path)
|
|
parser.add_argument("--rows", type=int, required=True)
|
|
parser.add_argument("--cols", type=int, required=True)
|
|
parser.add_argument("--names", required=True)
|
|
parser.add_argument("--output-dir", type=Path, required=True)
|
|
parser.add_argument("--inset", type=float, default=0.94)
|
|
return parser.parse_args()
|
|
|
|
|
|
def remove_green(image: Image.Image) -> Image.Image:
|
|
rgba = image.convert("RGBA")
|
|
cleaned: list[tuple[int, int, int, int]] = []
|
|
for red, green, blue, alpha in rgba.getdata():
|
|
key_distance = max(red, 255 - green, blue)
|
|
green_dominant = green - red > 45 and green - blue > 45
|
|
if key_distance <= 90 or green_dominant:
|
|
cleaned.append((0, 0, 0, 0))
|
|
else:
|
|
cleaned.append((red, green, blue, alpha))
|
|
rgba.putdata(cleaned)
|
|
return rgba
|
|
|
|
|
|
def resize_alpha(image: Image.Image, pixels: int) -> Image.Image:
|
|
# Premultiplied resize prevents hidden chroma RGB from leaking through the
|
|
# Lanczos filter into otherwise clean visible edge pixels.
|
|
resized = image.convert("RGBa").resize(
|
|
(pixels, pixels), Image.Resampling.LANCZOS
|
|
).convert("RGBA")
|
|
cleaned: list[tuple[int, int, int, int]] = []
|
|
for red, green, blue, alpha in resized.getdata():
|
|
if green - red > 45 and green - blue > 45:
|
|
cleaned.append((0, 0, 0, 0))
|
|
else:
|
|
cleaned.append((red, green, blue, alpha))
|
|
resized.putdata(cleaned)
|
|
return resized
|
|
|
|
|
|
def main() -> None:
|
|
args = parse_args()
|
|
if args.rows < 1 or args.cols < 1 or not 0 < args.inset <= 1:
|
|
raise SystemExit("invalid grid or inset")
|
|
names = [name.strip() for name in args.names.split(",") if name.strip()]
|
|
if len(names) != args.rows * args.cols:
|
|
raise SystemExit("name count must equal rows * cols")
|
|
|
|
source = Image.open(args.image).convert("RGBA")
|
|
width, height = source.size
|
|
args.output_dir.mkdir(parents=True, exist_ok=True)
|
|
for index, name in enumerate(names):
|
|
row, col = divmod(index, args.cols)
|
|
left = round(col * width / args.cols)
|
|
right = round((col + 1) * width / args.cols)
|
|
top = round(row * height / args.rows)
|
|
bottom = round((row + 1) * height / args.rows)
|
|
cell_width, cell_height = right - left, bottom - top
|
|
crop_width = round(cell_width * args.inset)
|
|
crop_height = round(cell_height * args.inset)
|
|
center_x, center_y = (left + right) / 2, (top + bottom) / 2
|
|
box = (
|
|
round(center_x - crop_width / 2),
|
|
round(center_y - crop_height / 2),
|
|
round(center_x + crop_width / 2),
|
|
round(center_y + crop_height / 2),
|
|
)
|
|
asset = remove_green(source.crop(box))
|
|
resize_alpha(asset, 128).save(args.output_dir / f"{name}@1x.png")
|
|
resize_alpha(asset, 256).save(args.output_dir / f"{name}@2x.png")
|
|
|
|
print(f"GREEN_GRID_PROCESS_OK assets={len(names) * 2} output={args.output_dir}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|