feat(h5): apply fourth feedback and tighten local capture validation
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Split the original PSD scan artwork for deterministic, code-driven motion."""
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from PIL import Image, ImageDraw
|
||||
from psd_tools import PSDImage
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("psd", type=Path)
|
||||
args = parser.parse_args()
|
||||
psd = PSDImage.open(args.psd)
|
||||
page = psd[9]
|
||||
if page.name != "扫描" or page[2][1].name != "矩形 661":
|
||||
raise ValueError("Unexpected scan layout; nothing exported")
|
||||
left, top, right, bottom = page.bbox
|
||||
viewport = (left, top + 248, right, bottom)
|
||||
source = page[2][2]
|
||||
source.visible = False
|
||||
base = page.composite(viewport=viewport).convert("RGB")
|
||||
# Recover the connected white face from the unmodified cached PSD pixels.
|
||||
# Disconnected frame/text/icon whites must never become part of the mask.
|
||||
candidate = Image.eval(base.convert("L"), lambda v: 255 if v > 245 else 0)
|
||||
ImageDraw.floodfill(candidate, (540, 720), 128)
|
||||
mask = Image.eval(candidate, lambda v: 255 if v == 128 else 0)
|
||||
if mask.getbbox() is None or mask.getpixel((540, 1200)) != 255:
|
||||
raise ValueError(f"PSD white face was lost to cross-layer clipping: bbox={mask.getbbox()}, seed={base.getpixel((540, 720))}, lower={base.getpixel((540, 1200))}")
|
||||
target = ROOT / "h5/assets/img/ui-20261012"
|
||||
target.mkdir(parents=True, exist_ok=True)
|
||||
alpha_mask = Image.new("RGBA", mask.size, (255, 255, 255, 0))
|
||||
alpha_mask.putalpha(mask)
|
||||
assets = []
|
||||
for name, image in [("scan-base.webp", base), ("scan-face-mask.png", alpha_mask)]:
|
||||
output = target / name
|
||||
if output.suffix == ".webp":
|
||||
image.save(output, lossless=True, method=6)
|
||||
else:
|
||||
image.save(output, optimize=True)
|
||||
data = output.read_bytes()
|
||||
assets.append({"file": str(output.relative_to(ROOT)), "size": list(image.size),
|
||||
"bytes": len(data), "sha256": hashlib.sha256(data).hexdigest()})
|
||||
print(f"Exported {name}: {image.size}, {len(data)} bytes", flush=True)
|
||||
digest = hashlib.sha256()
|
||||
with args.psd.open("rb") as stream:
|
||||
for chunk in iter(lambda: stream.read(8 * 1024 * 1024), b""):
|
||||
digest.update(chunk)
|
||||
manifest = {"source": str(args.psd), "source_sha256": digest.hexdigest(),
|
||||
"source_page": "9", "hidden_scan_layer": "9/2/2", "viewport": list(viewport),
|
||||
"face_bbox": list(mask.getbbox()), "cycle_ms": 1000, "yellow": "#F5F078",
|
||||
"assets": assets}
|
||||
design = ROOT / "design/ui-20261012"
|
||||
(design / "scan-motion-manifest.json").write_text(json.dumps(manifest, ensure_ascii=False, indent=2) + "\n")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user