"""Pre-generate scanvas_S.ico from Design/logo_V2.png for PyInstaller spec. Called by build.bat before PyInstaller runs. Standalone so encoding issues in .bat don't break it. Mirrors SCanvasApp._setup_window_icon logic. """ from __future__ import annotations import sys from pathlib import Path import numpy as np from PIL import Image from scipy import ndimage as nd def main() -> int: root = Path(__file__).resolve().parent src = root / "Design" / "logo_V2.png" dst_dir = root / "cache" / "icons" dst_dir.mkdir(parents=True, exist_ok=True) ico = dst_dir / "scanvas_S.ico" if not src.exists(): print(f"[icon] source not found: {src}") return 1 pil = Image.open(src).convert("RGBA") arr = np.asarray(pil).copy() v = np.maximum.reduce([arr[..., 0], arr[..., 1], arr[..., 2]]) arr[..., 3] = np.where(v < 90, 0, arr[..., 3]) # Crop left ~32% to isolate the 'S' letter crop_w = min(arr.shape[1], 850) carr = arr[:, :crop_w, :] mask = carr[..., 3] > 100 labeled, ncomp = nd.label(mask) if ncomp == 0: print("[icon] no foreground content found") return 1 sizes = nd.sum(mask, labeled, range(1, ncomp + 1)) max_size = float(sizes.max()) keep = [i + 1 for i, s in enumerate(sizes) if s >= max_size * 0.1] keep_mask = np.isin(labeled, keep) cc = carr.copy() cc[..., 3] = np.where(keep_mask, carr[..., 3], 0) ys, xs = np.where(keep_mask) cropped = Image.fromarray( cc[int(ys.min()):int(ys.max()) + 1, int(xs.min()):int(xs.max()) + 1, :], "RGBA", ) cw, ch = cropped.size side = max(cw, ch) pad = max(int(side * 0.04), 4) sp = side + 2 * pad sq = Image.new("RGBA", (sp, sp), (0, 0, 0, 0)) sq.paste(cropped, ((sp - cw) // 2, (sp - ch) // 2), cropped) sq.save( ico, format="ICO", sizes=[(16, 16), (32, 32), (48, 48), (64, 64), (128, 128), (256, 256)], ) print(f"[icon] {ico} ({ico.stat().st_size} bytes)") return 0 if __name__ == "__main__": sys.exit(main())