#!/usr/bin/env python3 r"""golden_compare.py — C 档黄金图对比(呈现层测试分级·渲染闸周期件)。 用法: python tools/golden_compare.py <黄金图目录> <新截图目录> [屏清单...] 默认屏清单 = shot_menu.png shot_camp.png shot_level.png shot_battle.png。 判据(容差口径,v1 冻结): ① 四屏存在且尺寸一致(与黄金图逐屏比对); ② 逐像素灰度差 >16 的像素占比 ≤2%(抗锯齿/微噪声容差带——结构级变化必超带); ③ changed 占比 >2% 即 FAIL(视觉回归信号——逐屏列差异热区提示)。 基准冻结于 v3.5 视觉落码+营地终稿+分辨率案甲后;资产升档换代时显式重制基准。 退出码: 0 = 全过 / 1 = 有失败。 """ import sys from pathlib import Path from PIL import Image, ImageChops TOLERANCE = 16 # 灰度差容差(单通道) FAIL_RATIO = 0.02 # changed 占比失败线 def compare(base: Path, curr: Path, name: str, failures: list) -> None: pb, pc = base / name, curr / name if not pb.exists(): failures.append("%s: 黄金图缺失(基准未建)" % name) return if not pc.exists(): failures.append("%s: 新截图缺失" % name) return a = Image.open(pb).convert("L") b = Image.open(pc).convert("L") if a.size != b.size: failures.append("%s: 尺寸 %s != 基准 %s" % (name, b.size, a.size)) return diff = ImageChops.difference(a, b) hist = diff.histogram() total = a.size[0] * a.size[1] changed = sum(hist[TOLERANCE + 1:]) / total print("[golden] %s — changed=%.2f%%(容差 ≤%.0f 差/≤%.0f%%)%s" % ( name, changed * 100, TOLERANCE, FAIL_RATIO * 100, "OK" if changed <= FAIL_RATIO else "FAIL")) if changed > FAIL_RATIO: failures.append("%s: changed %.2f%% > %.0f%%(视觉回归——核对热区后重制或修复)" % (name, changed * 100, FAIL_RATIO * 100)) def main() -> int: if len(sys.argv) < 3: print("用法: python tools/golden_compare.py <黄金图目录> <新截图目录> [屏清单...]") return 1 base, curr = Path(sys.argv[1]), Path(sys.argv[2]) shots = sys.argv[3:] or ["shot_menu.png", "shot_camp.png", "shot_level.png", "shot_battle.png"] failures = [] for name in shots: compare(base, curr, name, failures) if failures: print("[golden] FAIL——") for f in failures: print(" - %s" % f) return 1 print("[golden] PASS(%d 屏黄金图对比全过)" % len(shots)) return 0 if __name__ == "__main__": sys.exit(main())