文件预览

color_trim.py

查看 Sprite Tools 技能包中的文件内容。

文件内容

scripts/color_trim.py

#!/usr/bin/env python3
"""
color_trim.py — Crop fixed-color borders from images.
Uses the top-left pixel (0,0) as the border color, crops inward.

Usage:
  python3 color_trim.py <input.png> <output.png> [--atol N] [--batch] [--pad N]

  # White background (default atol=10)
  python3 color_trim.py /tmp/icon.png /tmp/icon_nobg.png

  # Gray background
  python3 color_trim.py /tmp/icon.png /tmp/icon_nobg.png --atol 20

  # Batch mode
  python3 color_trim.py /tmp/icons/ /tmp/icons_nobg/ --batch --atol 10 --pad 2
"""

import sys
import os
from pathlib import Path
import numpy as np
from PIL import Image


def trim_by_border_color(input_path, output_path, atol=10, pad=0):
    """Crop image by detecting border color from top-left corner."""
    img = Image.open(input_path)
    arr = np.array(img)
    h, w = arr.shape[:2]

    # Get border color from top-left pixel
    border_color = tuple(arr[0, 0])
    if len(border_color) == 4 and arr.shape[2] == 4:
        # RGBA: compare RGB only for background detection
        border_rgb = border_color[:3]
    else:
        border_rgb = border_color[:3]

    # Build mask: pixels that differ from border (beyond atol)
    diff = np.abs(arr[:, :, :3].astype(int) - np.array(border_rgb).astype(int))
    mask = np.any(diff > atol, axis=2)

    rows = np.any(mask, axis=1)
    cols = np.any(mask, axis=0)

    if not rows.any() or not cols.any():
        print(f"  ⚠️  No content found in {input_path}, saving as-is")
        img.save(output_path)
        return

    rmin, rmax = np.where(rows)[0][[0, -1]]
    cmin, cmax = np.where(cols)[0][[0, -1]]

    x1 = max(0, cmin - pad)
    y1 = max(0, rmin - pad)
    x2 = min(w, cmax + 1 + pad)
    y2 = min(h, rmax + 1 + pad)

    cropped = img.crop((x1, y1, x2, y2))
    cropped.save(output_path)
    orig_size = img.size
    new_size = cropped.size
    border_hex = '#{:02x}{:02x}{:02x}'.format(*border_rgb)
    print(f"  ✅ {Path(input_path).name}: border={border_hex} atol={atol}, {orig_size[0]}x{orig_size[1]} → {new_size[0]}x{new_size[1]}")


def process_batch(input_dir, output_dir, atol=10, pad=0):
    input_dir = Path(input_dir)
    output_dir = Path(output_dir)
    output_dir.mkdir(parents=True, exist_ok=True)

    files = sorted(input_dir.glob("*.png")) + sorted(input_dir.glob("*.jpg")) + sorted(input_dir.glob("*.webp"))
    if not files:
        print(f"  ⚠️  No images found in {input_dir}")
        return

    print(f"Batch: {len(files)} images → {output_dir}/")
    for f in files:
        out = output_dir / f.name
        trim_by_border_color(str(f), str(out), atol, pad)


if __name__ == "__main__":
    args = sys.argv[1:]

    atol = 10
    pad = 0
    batch = False

    i = 0
    clean_args = []
    while i < len(args):
        if args[i] == '--atol' and i + 1 < len(args):
            atol = int(args[i + 1])
            i += 2
        elif args[i] == '--pad' and i + 1 < len(args):
            pad = int(args[i + 1])
            i += 2
        elif args[i] == '--batch':
            batch = True
            i += 1
        else:
            clean_args.append(args[i])
            i += 1

    if len(clean_args) < 2:
        print(__doc__)
        sys.exit(1)

    input_path = clean_args[0]
    output_path = clean_args[1]

    if batch:
        process_batch(input_path, output_path, atol, pad)
    else:
        trim_by_border_color(input_path, output_path, atol, pad)