code wiki / _hdl_build / nx_galx_thumb_oneshot.nx
nx_galx_thumb_oneshot.nx source
↩ module page · 110 lines · 6448 B
1// nx_galx_thumb.nx -- SOVEREIGN gallery THUMBNAIL generator (kills the full-res-into-160px-cells waste).
2//
3// The gallery serves /img/<cid> = the FULL-RESOLUTION image into the grid (nx_gallery_serve.nx:2
4// "no thumbnails") -> a grid page downloads ~60 multi-MB images = the dominant "performance is awful".
5// World-class galleries NEVER serve full-res into a grid (research: thumbnail pyramids / responsive
6// variants). This composes PROVEN sovereign organs (DRY #15, zero 3rd-party): nx_png_decode ->
7// nx_img_scale_bilinear (the nc=3 path nx_img_convert already ships) -> nx_png_write_rgb. One-shot CLI;
8// the gallery generates-on-first-view + caches, so each thumbnail is computed once.
9//
10// usage: nx_galx_thumb <src.png> <out.png> [maxdim=384] (aspect-preserving, never upscales)
11// expect_exit: 0 ok ; nonzero = unreadable/undecodable/unsupported (caller falls back to full-res)
12// license_tier: ORIGINAL
13import "nx_syscalls.nx"
14import "nx_png_decoder.nx"
15import "nx_img_scale.nx"
16import "nx_png_write.nx"
17import "nx_jpeg_color_write.nx" // jcw_write_color (baseline color JPEG)
18import "nx_quant_table.nx" // nx_qt_luma / nx_qt_chroma
19import "nx_jpeg_huff_enc.nx" // jhe_* (luma Huffman tables + code gen)
20import "nx_zigzag.nx" // nx_zigzag_init
21import "nx_dct8.nx" // nx_dct8_init
22const K_MAGIC_8192: i64 = 8192
23
24func gt_puts(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(2, s, n); return 0 }
25func gt_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; if c < 48 { return v } if c > 57 { return v } v = v * 10 + (c - 48); i = i + 1 } return v }
26// rule-18: log the FAILING PATH + the numeric error code so the remaining decode-error slice is MEASURABLE
27// (bucket by code: unsupported-depth vs interlace vs corrupt vs bad-CRC). A count is worthless without WHY.
28func gt_num(v: i64) -> i64 { let b: *u8=sys_mmap(28); var m: i64=v; if m<0{m=0-m}; let t: *u8=sys_mmap(28); var k: i64=0; if m==0{t[0]=48;k=1}; while m>0{t[k]=(48+(m%10)) as u8;m=m/10;k=k+1}; var i: i64=0; while i<k{b[i]=t[k-1-i];i=i+1}; sys_write(2,b,k); return 0 }
29
30func main(argc: i64, argv: *i64) -> i64 {
31 if argc < 3 { gt_puts("usage: nx_galx_thumb <src.png> <out.png> [maxdim]\n" as *u8); return 2 }
32 let src: *u8 = argv[1] as *u8
33 let out: *u8 = argv[2] as *u8
34 var maxdim: i64 = 384
35 if argc > 3 { let m: i64 = gt_atoi(argv[3] as *u8); if m > 0 { maxdim = m } }
36
37 // one-shot process (no fork-loop) -> sys_read_file's big mmap is fine here
38 let szbox: *i64 = sys_mmap(16) as *i64
39 let data: *u8 = sys_read_file(src, szbox)
40 if (data as i64) == 0 { gt_puts("thumb: cannot read src src=" as *u8); gt_puts(src); gt_puts("\n" as *u8); return 1 }
41
42 let res: *NxPngResult = nx_png_decode(data, szbox[0])
43 if (res as i64) == 0 { gt_puts("thumb: decode null src=" as *u8); gt_puts(src); gt_puts("\n" as *u8); return 3 }
44 if res.error_code != 0 { gt_puts("thumb: decode error src=" as *u8); gt_puts(src); gt_puts(" code=" as *u8); gt_num(res.error_code); gt_puts("\n" as *u8); return 4 }
45 let w: i64 = res.header.width
46 let h: i64 = res.header.height
47 let nc: i64 = res.n_channels
48 if w < 1 { return 5 }
49 if h < 1 { return 5 }
50
51 // target dims: longest side -> maxdim, aspect-preserving, never upscale
52 var dw: i64 = w
53 var dh: i64 = h
54 if w >= h {
55 if w > maxdim { dw = maxdim; dh = (h * maxdim) / w }
56 } else {
57 if h > maxdim { dh = maxdim; dw = (w * maxdim) / h }
58 }
59 if dw < 1 { dw = 1 }
60 if dh < 1 { dh = 1 }
61 // round dims DOWN to a multiple of 8 (baseline JPEG encodes whole 8x8 blocks); min 8. <=7px loss, imperceptible.
62 dw = (dw / 8) * 8; if dw < 8 { dw = 8 }
63 dh = (dh / 8) * 8; if dh < 8 { dh = 8 }
64
65 // normalize the decoded pixels to 3-channel RGB BEFORE scaling (scale at nc=3 = the proven path).
66 var src3: *u8 = res.pixels
67 if nc != 3 {
68 src3 = sys_mmap(w * h * 3 + 16)
69 var i: i64 = 0
70 let npx: i64 = w * h
71 while i < npx {
72 let so: i64 = i * nc
73 let d3: i64 = i * 3
74 if nc == 1 {
75 let g: u8 = res.pixels[so]; src3[d3] = g; src3[d3 + 1] = g; src3[d3 + 2] = g
76 } else {
77 if nc >= 3 {
78 src3[d3] = res.pixels[so]; src3[d3 + 1] = res.pixels[so + 1]; src3[d3 + 2] = res.pixels[so + 2]
79 } else {
80 let g2: u8 = res.pixels[so]; src3[d3] = g2; src3[d3 + 1] = g2; src3[d3 + 2] = g2
81 }
82 }
83 i = i + 1
84 }
85 }
86
87 let dst: *u8 = sys_mmap(dw * dh * 3 + 16)
88 if nx_img_scale_bilinear(src3, w, h, 3, dst, dw, dh) != 0 { gt_puts("thumb: scale failed\n" as *u8); return 6 }
89 // encode a baseline COLOR JPEG (q72) -- far smaller than lossless PNG for photos. Set up the shared
90 // luma Huffman tables + luma/chroma quant + zigzag + DCT (same as the proven jpeg-write gate).
91 let dcB: *i64 = sys_mmap(20*8) as *i64; let dcV: *i64 = sys_mmap(20*8) as *i64
92 let acB: *i64 = sys_mmap(20*8) as *i64; let acV: *i64 = sys_mmap(200*8) as *i64
93 let ndc: i64 = jhe_dc_bits(dcB); jhe_dc_val(dcV); let nac: i64 = jhe_ac_bits(acB); jhe_ac_val(acV)
94 let dcCO: *i64 = sys_mmap(256*8) as *i64; let dcSI: *i64 = sys_mmap(256*8) as *i64
95 let acCO: *i64 = sys_mmap(256*8) as *i64; let acSI: *i64 = sys_mmap(256*8) as *i64
96 jhe_gen(dcB, dcV, ndc, dcCO, dcSI); jhe_gen(acB, acV, nac, acCO, acSI)
97 let qtL: *i64 = sys_mmap(64*8) as *i64; nx_qt_luma(qtL, 72)
98 let qtC: *i64 = sys_mmap(64*8) as *i64; nx_qt_chroma(qtC, 72)
99 let to_zz: *i64 = sys_mmap(64*8) as *i64; let from_zz: *i64 = sys_mmap(64*8) as *i64; nx_zigzag_init(to_zz, from_zz)
100 let DM: *i64 = sys_mmap(64*8) as *i64; nx_dct8_init(DM)
101 let scratch: *i64 = sys_mmap(64*8) as *i64; let tti: *i64 = sys_mmap(8*8) as *i64; let tto: *i64 = sys_mmap(8*8) as *i64
102 let jout: *u8 = sys_mmap(dw * dh * 3 + K_MAGIC_8192)
103 let jlen: i64 = jcw_write_color(jout, dw, dh, dst, qtL, qtC, dcB, dcV, acB, acV, dcCO, dcSI, acCO, acSI, to_zz, from_zz, DM, scratch, tti, tto)
104 if jlen <= 0 { gt_puts("thumb: jpeg encode failed\n" as *u8); return 7 }
105 let ofd: i64 = sys_openat_wr(out, 0x1a4)
106 if ofd < 0 { gt_puts("thumb: out open failed\n" as *u8); return 8 }
107 sys_write(ofd, jout, jlen); sys_close(ofd)
108 gt_puts("thumb OK\n" as *u8)
109 return 0
110}