#!/usr/bin/env python3 """Derive H5 crops from exported PSD assets; never crop the original exports.""" from pathlib import Path from PIL import Image import json import cv2 import numpy as np root = Path(__file__).resolve().parents[1] / 'h5/assets/img/ui-20261009' for name in ['bg-splash', 'bg-login', 'bg-profile', 'bg-history', 'bg-report']: source = Image.open(root / (name + '.jpg')) # Native status/navigation area belongs to the host, not the H5 document. top = 253 if name == 'bg-report' else 248 source.crop((0, top, source.width, source.height)).save(root / (name + '-h5.jpg'), quality=92, optimize=True) # Mobile delivery variant; preserve the full-resolution PSD exports/JPEGs. Image.open(root / (name + '-h5.jpg')).save(root / (name + '-h5.webp'), quality=85, method=6) geometry = [] for direction in ['front', 'left', 'right']: alpha = np.array(Image.open(root / ('camera-mask-' + direction + '.png')).getchannel('A')) contours, _ = cv2.findContours((alpha < 100).astype('uint8'), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) contour = max((c for c in contours if 500 < cv2.boundingRect(c)[1] < 1600), key=cv2.contourArea) points = cv2.approxPolyDP(contour, 1.4, True).reshape(-1, 2) outline = 'M' + 'L'.join(f'{x/2.88:.2f},{y/2.88:.2f}' for x, y in points) + 'Z' guide_alpha = np.array(Image.open(root / ('camera-guide-' + direction + '.png')).getchannel('A')) count, labels, stats, _ = cv2.connectedComponentsWithStats((guide_alpha > 100).astype('uint8')) guides = [] for i in range(1, count): x, y, w, h, area = stats[i] if area < 50: continue points = [] if w > h: for col in range(x, x+w, 3): rows = np.where(labels[:, col] == i)[0] if len(rows): points.append((col, rows.mean())) else: for row in range(y, y+h, 3): cols = np.where(labels[row, :] == i)[0] if len(cols): points.append((cols.mean(), row)) guides.append('M' + 'L'.join(f'{px/2.88:.2f},{py/2.88:.2f}' for px, py in points)) geometry.append({'outline': outline, 'guides': guides}) (root.parents[2] / 'js/camera-geometry.js').write_text('window.CameraGeometry = ' + json.dumps(geometry) + ';\n')