GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git
scripts/web-probe/compare.py (2.1K)
1 #!/usr/bin/env python3
2 """compare.py native.png web.rgba W H [bg] [--mask X,Y,W,H …]: composite the web capture
3 (premultiplied RGBA8, as a WebGPU canvas with alpha_mode=premultiplied holds
4 it) over the compositor background the native screenshot shows behind the
5 window (a grey level, default 63: headless sway's #3f3f3f), and report how far
6 the two frames are apart, per channel.
7
8 The native screenshot must be the window alone at its origin. A cursor sprite
9 in it is a difference this cannot tell from the renderer's: keep the pointer
10 off the window, or `--mask` the box the sprite covers (repeatable). Hiding
11 the cursor in the compositor is no answer — sway's `hide_cursor` clears the
12 pointer focus, so the app sees a leave and drops its hover. Needs
13 ImageMagick's `convert`."""
14 import subprocess, sys
15 args = sys.argv[1:]
16 masks = []
17 while '--mask' in args:
18 i = args.index('--mask')
19 masks.append(tuple(int(v) for v in args[i + 1].split(',')))
20 del args[i:i + 2]
21 native_png, web_path, W, H = args[0], args[1], int(args[2]), int(args[3])
22 bg = int(args[4]) if len(args) > 4 else 63
23 masked = lambda x, y: any(mx <= x < mx + mw and my <= y < my + mh for mx, my, mw, mh in masks)
24 native = subprocess.run(['convert', native_png, '-alpha', 'off', 'rgb:-'], capture_output=True, check=True).stdout
25 web = open(web_path, 'rb').read()
26 assert len(native) == W * H * 3 and len(web) == W * H * 4, (len(native), len(web))
27 hist = {}
28 worst = (0, None)
29 for i in range(W * H):
30 if masks and masked(i % W, i // W):
31 continue
32 a = web[4 * i + 3]
33 for c in range(3):
34 w = web[4 * i + c] + bg * (255 - a) / 255.0
35 d = abs(round(w) - native[3 * i + c])
36 hist[d] = hist.get(d, 0) + 1
37 if d > worst[0]:
38 worst = (d, (i % W, i // W))
39 px_over = lambda t: sum(n for d, n in hist.items() if d > t)
40 print(f"channels compared: {sum(hist.values())}")
41 print("difference histogram (levels of 255):", dict(sorted(hist.items())))
42 print(f"channels off by >1: {px_over(1)}, >2: {px_over(2)}, >4: {px_over(4)}, >8: {px_over(8)}")
43 print(f"worst: {worst[0]} at {worst[1]}")