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}