#!/usr/bin/env python3 r"""golden_board_crop.py — 棋盘 golden 前置裁剪。 board_shot_probe 截整屏视口(get_viewport().get_texture()),golden 逐像素对比前须裁出棋盘区。 本工具按 board_meta.json 取样坐标(board_shot_probe 输出的棋盘视图实测全局矩形)裁剪, **禁硬编码像素**(口径:布局改动只须重跑探针刷新 meta,裁剪代码零改动)。 用法: python tools/golden_board_crop.py <截图目录> <输出目录> 输出: <输出目录>/board_.png(tag=meta 键,如 L1_dungeon)。 容错:矩形越界按图像边界收拢;meta 键缺失/矩形退化(宽高≤0)= 报错退出 1(取证缺失不静默)。 退出码: 0 = 全部裁出 / 1 = 有失败。 """ import json import sys from pathlib import Path from PIL import Image def main() -> int: if len(sys.argv) != 4: print("用法: python tools/golden_board_crop.py <截图目录> <输出目录>") return 1 meta_p, src_d, dst_d = Path(sys.argv[1]), Path(sys.argv[2]), Path(sys.argv[3]) if not meta_p.exists(): print("[golden_crop] FAIL — board_meta.json 缺失: %s" % meta_p) return 1 meta = json.loads(meta_p.read_text(encoding="utf-8")) if not meta: print("[golden_crop] FAIL — board_meta.json 空表(取样坐标零在案)") return 1 dst_d.mkdir(parents=True, exist_ok=True) failures = [] for tag, r in meta.items(): src = src_d / ("board_%s.png" % tag) if not src.exists(): failures.append("%s: 截图缺失 %s" % (tag, src)) continue img = Image.open(src).convert("RGB") try: x, y, w, h = int(r["x"]), int(r["y"]), int(r["w"]), int(r["h"]) except (KeyError, TypeError, ValueError): failures.append("%s: meta 矩形字段缺失/非法 %s" % (tag, r)) continue # 越界收拢到图像边界(全局 rect 可能触视口边) box = (max(0, x), max(0, y), min(img.size[0], x + w), min(img.size[1], y + h)) if box[2] - box[0] <= 0 or box[3] - box[1] <= 0: failures.append("%s: 矩形退化 %s(img %s)" % (tag, box, img.size)) continue img.crop(box).save(dst_d / ("board_%s.png" % tag)) print("[golden_crop] %s — %s -> %s(%dx%d)" % (tag, box, dst_d / ("board_%s.png" % tag), box[2] - box[0], box[3] - box[1])) if failures: print("[golden_crop] FAIL——") for f in failures: print(" - %s" % f) return 1 print("[golden_crop] PASS(%d 张棋盘区裁剪全出)" % len(meta)) return 0 if __name__ == "__main__": sys.exit(main())