#!/usr/bin/env python3 """Fail if any recorded frame contains a near-white opaque block. This is the image-level regression guard for the P13/P14 lantern blow-out: a premultiplied-alpha mistake painted ~75x75 pure white (255,255,255) squares where the night lantern glow should fall off softly. A "white block" is any window of at least MIN_SIDE x MIN_SIDE pixels where every pixel is fully opaque (alpha >= 250) and every colour channel is >= 250. Usage: python3 scripts/check-no-white-blocks.py [more...] Exit code 0 when clean, 1 when a block is found, 2 on usage/read error. """ import os import sys import numpy as np from PIL import Image MIN_SIDE = 64 CHANNEL_MIN = 250 ALPHA_MIN = 250 def first_white_block(path, min_side=MIN_SIDE): image = Image.open(path) rgba = np.asarray(image.convert("RGBA"), dtype=np.uint8) if rgba.shape[0] < min_side or rgba.shape[1] < min_side: return None white_opaque = ( (rgba[:, :, 0] >= CHANNEL_MIN) & (rgba[:, :, 1] >= CHANNEL_MIN) & (rgba[:, :, 2] >= CHANNEL_MIN) & (rgba[:, :, 3] >= ALPHA_MIN) ).astype(np.int64) # Summed-area table so any k x k window is one lookup. integral = np.zeros((white_opaque.shape[0] + 1, white_opaque.shape[1] + 1), dtype=np.int64) integral[1:, 1:] = white_opaque.cumsum(0).cumsum(1) rows, cols = white_opaque.shape for y in range(0, rows - min_side + 1): for x in range(0, cols - min_side + 1): total = ( integral[y + min_side, x + min_side] - integral[y, x + min_side] - integral[y + min_side, x] + integral[y, x] ) if total == min_side * min_side: return (x, y, min_side, min_side) return None def iter_pngs(targets): for target in targets: if os.path.isdir(target): for root, _, files in os.walk(target): for name in sorted(files): if name.lower().endswith(".png"): yield os.path.join(root, name) else: yield target def main(argv): if len(argv) < 2: sys.stderr.write(__doc__) return 2 checked = 0 failures = [] for path in iter_pngs(argv[1:]): try: block = first_white_block(path) except Exception as error: # noqa: BLE001 - report and continue sys.stderr.write(f"unreadable: {path}: {error}\n") return 2 checked += 1 if block is not None: x, y, side, _ = block failures.append(f"{path}: white block {side}x{side} at ({x},{y})") for line in failures: print(f"FAIL {line}") if failures: print(f"checked={checked} failures={len(failures)}") return 1 print(f"checked={checked} failures=0 OK") return 0 if __name__ == "__main__": raise SystemExit(main(sys.argv))