code wiki / _hdl_build / nx_img_sbs.nx
nx_img_sbs.nx source
↩ module page · 62 lines · 3119 B
1// nx_img_sbs.nx -- sovereign SIDE-BY-SIDE evidence compositor: oracle screenshot | Nishi screenshot into
2// ONE image, so a visual-parity claim is a single artifact a human can eyeball (the "show me the comp"
3// primitive of the evidence workstream). Crops each input to the same row window (skips browser chrome
4// bars), places A left / B right with a neutral gutter. usage:
5// nx_img_sbs <A.png> <ay> <B.png> <by> <H> <out.png>
6// A rows [ay..ay+H) left, B rows [by..by+H) right, gutter 20px #303845. license_tier: ORIGINAL
7import "nx_visual_diff.nx"
8import "nx_itoa_lib.nx" // shared MSB-first emitter (zero-alloc)
9import "nx_png_write.nx"
10
11const SBS_GUTTER: i64 = 20
12
13func sb_w(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 }
14// MIGRATED to the shared emitter (debt 1785563586). The old body mmapped a scratch buffer
15// per call and never freed it. At PAGE granularity that is 4096B leaked PER CALL -- the
16// defect that took 28.5GB of a 36GB host in nx_ts_lumadiff (2MB input, ~3.66M calls).
17// nxi_* is MSB-first, allocates NOTHING, and emits identical bytes including the sign.
18func sb_n(v: i64) -> i64 { nxi_out(v); return 0 }
19func sb_atoi(s: *u8) -> i64 { var v: i64=0; var i: i64=0; while s[i]!=(0 as u8){ let c: i64=(s[i] as i64)&255; if c>=48 { if c<=57 { v=v*10+(c-48) } } i=i+1 } return v }
20
21// copy H rows of src (starting src row yoff) into dst at (dx,0); dst is RGB w=dw
22func sb_blit(dst: *u8, dw: i64, im: *VdImg, yoff: i64, dx: i64, hh: i64) -> i64 {
23 var y: i64 = 0
24 while y < hh {
25 var x: i64 = 0
26 while x < im.w {
27 let so: i64 = ((y + yoff) * im.w + x) * im.nc
28 let dof: i64 = (y * dw + dx + x) * 3
29 dst[dof] = im.px[so]
30 dst[dof+1] = im.px[so+1]
31 dst[dof+2] = im.px[so+2]
32 x = x + 1
33 }
34 y = y + 1
35 }
36 return 0
37}
38
39func main(argc: i64, argv: *i64) -> i64 {
40 if argc < 7 { sb_w("usage: nx_img_sbs <A.png> <ay> <B.png> <by> <H> <out.png>\n" as *u8); sys_exit(2); return 2 }
41 let a: *VdImg = vd_load(argv[1] as *u8)
42 if (a as i64) == 0 { sb_w("cannot load A\n" as *u8); sys_exit(3); return 3 }
43 let ay: i64 = sb_atoi(argv[2] as *u8)
44 let b: *VdImg = vd_load(argv[3] as *u8)
45 if (b as i64) == 0 { sb_w("cannot load B\n" as *u8); sys_exit(3); return 3 }
46 let by: i64 = sb_atoi(argv[4] as *u8)
47 var hh: i64 = sb_atoi(argv[5] as *u8)
48 if ay + hh > a.h { hh = a.h - ay }
49 if by + hh > b.h { hh = b.h - by }
50 if hh <= 0 { sb_w("empty crop\n" as *u8); sys_exit(4); return 4 }
51 let dw: i64 = a.w + SBS_GUTTER + b.w
52 let dst: *u8 = sys_mmap((dw * hh * 3) as nx_size)
53 // gutter fill #303845 (neutral slate)
54 var i: i64 = 0
55 while i < dw * hh { dst[i*3]=48 as u8; dst[i*3+1]=56 as u8; dst[i*3+2]=69 as u8; i = i + 1 }
56 sb_blit(dst, dw, a, ay, 0, hh)
57 sb_blit(dst, dw, b, by, a.w + SBS_GUTTER, hh)
58 nx_png_write_rgb(argv[6] as *u8, dst, dw, hh)
59 sb_w("sbs " as *u8); sb_n(dw); sb_w("x" as *u8); sb_n(hh); sb_w(" -> " as *u8); sb_w(argv[6] as *u8); sb_w("\n" as *u8)
60 sys_exit(0)
61 return 0
62}