code wiki / _hdl_build / nx_reader_tile.nx

nx_reader_tile.nx source

↩ module page · 158 lines · 8890 B

1// nx_reader_tile.nx -- SOVEREIGN tiled-pyramid emitter (reader census gap: DZI/IIIF-class smooth mobile zoom of 2// huge manga/document scans). Given one page image (JPEG or PNG), emits a Deep-Zoom-style pyramid: for each level 3// 0..maxlevel (level maxlevel = full res, each lower level halves) it box-downscales the page and cuts it into 4// fixed T x T JPEG tiles. A mobile viewer then fetches ONLY the tiles visible at the current zoom = smooth zoom 5// without downloading the whole 8000px scan. Composes PROVEN sovereign organs (DRY, zero 3rd-party): universal 6// decode nx_img_bytes_to_rgb (JPEG+PNG) -> nx_img_scale_bilinear -> jcw_write_color. KEY: each level's dims are 7// rounded to a multiple of 8 and T is a multiple of 8, so EVERY tile (incl right/bottom edges) is a multiple of 8 8// -> the baseline JPEG encoder handles all tiles. This is a LIB (no main) so nx_reader_tile_gate exercises rt_emit 9// directly (nx_mgmt_upload lib+gate split). Fork-per-page in the daemon -> the per-tile mmaps die with the child. 10// license_tier: ORIGINAL 11import "nx_syscalls.nx" 12import "nx_img_bytes_to_rgb.nx" // nx_img_bytes_to_rgb (JPEG+PNG -> RGB) [+ png_decoder + jpeg_ascii] 13import "nx_img_dims.nx" // nx_img_dims (header W/H for JPEG/PNG/GIF/WebP) -> raw-fallback manifest sizing 14import "nx_img_scale.nx" // nx_img_scale_bilinear 15import "nx_jpeg_color_write.nx" // jcw_write_color 16import "nx_quant_table.nx" // nx_qt_luma / nx_qt_chroma 17import "nx_jpeg_huff_enc.nx" // jhe_* 18import "nx_zigzag.nx" // nx_zigzag_init 19import "nx_dct8.nx" // nx_dct8_init 20const K_MAGIC_1200: i64 = 1200 21const K_MAGIC_1600: i64 = 1600 22const K_MAGIC_1024: i64 = 1024 23const K_MAGIC_8192: i64 = 8192 24 25func rt_ap_str(dst: *u8, o: i64, s: *u8) -> i64 { var i: i64 = 0; while s[i] != (0 as u8) { dst[o + i] = s[i]; i = i + 1 } return o + i } 26func rt_ap_num(dst: *u8, o: i64, v: i64) -> i64 { 27 let t: *u8 = sys_mmap(24); var m: i64 = v; var k: i64 = 0 28 if m == 0 { t[0] = 48 as u8; k = 1 } 29 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 30 var w: i64 = o; var i: i64 = 0 31 while i < k { dst[w] = t[k - 1 - i]; w = w + 1; i = i + 1 } 32 return w 33} 34func rt_mkdir(path: *u8) -> i64 { __syscall(258, 0 - 100, path as i64, 0x1ed, 0, 0, 0); return 0 } 35func rt_writefile(path: *u8, buf: *u8, n: i64) -> i64 { 36 let fd: i64 = sys_openat_wr(path, 0x1a4) 37 if fd < 0 { return 0 - 1 } 38 sys_write(fd, buf, n); sys_close(fd) 39 return n 40} 41 42// RAW fallback: a page whose format the tiler can't decode (WebP, GIF, ...) must NOT 500 the reader. Write a 43// dz.json with "raw":1 so the viewer displays the page BROWSER-NATIVE (full color + pan/zoom); JPEG/PNG still get 44// the deep-zoom pyramid. This is the "usable mangas" guarantee (operator 2026-07-04). Returns RT_RAW. 45func rt_raw_manifest(data: *u8, n: i64, outdir: *u8) -> i64 { 46 let wh: *i64 = sys_mmap(16) as *i64 47 var rw: i64 = K_MAGIC_1200; var rh: i64 = K_MAGIC_1600 48 if nx_img_dims(data, n, wh) == 1 { if wh[0] > 0 { rw = wh[0] } if wh[1] > 0 { rh = wh[1] } } 49 rt_mkdir(outdir) 50 let man: *u8 = sys_mmap(512); var mo: i64 = 0 51 mo = rt_ap_str(man, mo, "{\"raw\":1,\"tilesize\":512,\"overlap\":0,\"width\":" as *u8); mo = rt_ap_num(man, mo, rw) 52 mo = rt_ap_str(man, mo, ",\"height\":" as *u8); mo = rt_ap_num(man, mo, rh) 53 mo = rt_ap_str(man, mo, ",\"maxlevel\":0}" as *u8) 54 let mp: *u8 = sys_mmap(K_MAGIC_1024); var mpo: i64 = rt_ap_str(mp, 0, outdir); mpo = rt_ap_str(mp, mpo, "/dz.json" as *u8); mp[mpo] = 0 as u8 55 rt_writefile(mp, man, mo) 56 return 0 - 7 57} 58 59// emit the tiled pyramid for src image into outdir. Returns tiles written (>=0), RT_RAW(-7) = raw fallback written, 60// or a negative error code (-1 read fail, -3 bad dims). 61func rt_emit(src: *u8, outdir: *u8, tilesize: i64) -> i64 { 62 var T: i64 = tilesize; if T < 8 { T = 512 }; T = (T / 8) * 8; if T < 8 { T = 8 } 63 64 let szbox: *i64 = sys_mmap(16) as *i64 65 let data: *u8 = sys_read_file(src, szbox) 66 if (data as i64) == 0 { return 0 - 1 } 67 let wh: *i64 = sys_mmap(16) as *i64 68 let full: *u8 = nx_img_bytes_to_rgb(data, szbox[0], wh) 69 if (full as i64) == 0 { return rt_raw_manifest(data, szbox[0], outdir) } // untileable format -> raw fallback (never 500) 70 let W: i64 = wh[0]; let H: i64 = wh[1] 71 if W < 1 { return 0 - 3 } 72 if H < 1 { return 0 - 3 } 73 74 // maxlevel = smallest k with (1<<k) >= max(W,H) 75 var mx: i64 = W; if H > mx { mx = H } 76 var maxlevel: i64 = 0 77 while (1 << maxlevel) < mx { maxlevel = maxlevel + 1 } 78 79 // ---- baseline color JPEG encoder tables (set up ONCE, reused for every tile) -- verbatim from nx_galx_thumb ---- 80 let dcB: *i64 = sys_mmap(20*8) as *i64; let dcV: *i64 = sys_mmap(20*8) as *i64 81 let acB: *i64 = sys_mmap(20*8) as *i64; let acV: *i64 = sys_mmap(200*8) as *i64 82 let ndc: i64 = jhe_dc_bits(dcB); jhe_dc_val(dcV); let nac: i64 = jhe_ac_bits(acB); jhe_ac_val(acV) 83 let dcCO: *i64 = sys_mmap(256*8) as *i64; let dcSI: *i64 = sys_mmap(256*8) as *i64 84 let acCO: *i64 = sys_mmap(256*8) as *i64; let acSI: *i64 = sys_mmap(256*8) as *i64 85 jhe_gen(dcB, dcV, ndc, dcCO, dcSI); jhe_gen(acB, acV, nac, acCO, acSI) 86 let qtL: *i64 = sys_mmap(64*8) as *i64; nx_qt_luma(qtL, 72) 87 let qtC: *i64 = sys_mmap(64*8) as *i64; nx_qt_chroma(qtC, 72) 88 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) 89 let DM: *i64 = sys_mmap(64*8) as *i64; nx_dct8_init(DM) 90 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 91 92 rt_mkdir(outdir) 93 94 // manifest dz.json -- viewer derives each level's dims with the SAME formula below. 95 let man: *u8 = sys_mmap(K_MAGIC_1024); var mo: i64 = 0 96 mo = rt_ap_str(man, mo, "{\"format\":\"jpg\",\"tilesize\":" as *u8); mo = rt_ap_num(man, mo, T) 97 mo = rt_ap_str(man, mo, ",\"overlap\":0,\"width\":" as *u8); mo = rt_ap_num(man, mo, W) 98 mo = rt_ap_str(man, mo, ",\"height\":" as *u8); mo = rt_ap_num(man, mo, H) 99 mo = rt_ap_str(man, mo, ",\"maxlevel\":" as *u8); mo = rt_ap_num(man, mo, maxlevel) 100 mo = rt_ap_str(man, mo, "}" as *u8) 101 let mpath: *u8 = sys_mmap(K_MAGIC_1024); var mpo: i64 = rt_ap_str(mpath, 0, outdir); mpo = rt_ap_str(mpath, mpo, "/dz.json" as *u8); mpath[mpo] = 0 as u8 102 rt_writefile(mpath, man, mo) 103 104 var total: i64 = 0 105 var level: i64 = 0 106 while level <= maxlevel { 107 let scale: i64 = 1 << (maxlevel - level) 108 var lw: i64 = (W + scale - 1) / scale 109 var lh: i64 = (H + scale - 1) / scale 110 lw = (lw / 8) * 8; if lw < 8 { lw = 8 } 111 lh = (lh / 8) * 8; if lh < 8 { lh = 8 } 112 let lvl: *u8 = sys_mmap(lw * lh * 3 + 16) 113 nx_img_scale_bilinear(full, W, H, 3, lvl, lw, lh) 114 115 // outdir/<level> 116 let ldir: *u8 = sys_mmap(K_MAGIC_1024); var ldo: i64 = rt_ap_str(ldir, 0, outdir) 117 ldir[ldo] = 47 as u8; ldo = ldo + 1; ldo = rt_ap_num(ldir, ldo, level); ldir[ldo] = 0 as u8 118 rt_mkdir(ldir) 119 120 let cols: i64 = (lw + T - 1) / T 121 let rows: i64 = (lh + T - 1) / T 122 var row: i64 = 0 123 while row < rows { 124 var col: i64 = 0 125 while col < cols { 126 let tx: i64 = col * T; let ty: i64 = row * T 127 var tw: i64 = T; if lw - tx < T { tw = lw - tx } 128 var th: i64 = T; if lh - ty < T { th = lh - ty } 129 let tile: *u8 = sys_mmap(tw * th * 3 + 16) 130 var yy: i64 = 0 131 while yy < th { 132 var xx: i64 = 0 133 while xx < tw { 134 let so: i64 = ((ty + yy) * lw + (tx + xx)) * 3 135 let do2: i64 = (yy * tw + xx) * 3 136 tile[do2] = lvl[so]; tile[do2 + 1] = lvl[so + 1]; tile[do2 + 2] = lvl[so + 2] 137 xx = xx + 1 138 } 139 yy = yy + 1 140 } 141 let jout: *u8 = sys_mmap(tw * th * 3 + K_MAGIC_8192) 142 let jlen: i64 = jcw_write_color(jout, tw, th, tile, qtL, qtC, dcB, dcV, acB, acV, dcCO, dcSI, acCO, acSI, to_zz, from_zz, DM, scratch, tti, tto) 143 if jlen > 0 { 144 // outdir/<level>/<col>_<row>.jpg 145 let tp: *u8 = sys_mmap(K_MAGIC_1024); var tpo: i64 = rt_ap_str(tp, 0, ldir) 146 tp[tpo] = 47 as u8; tpo = tpo + 1 147 tpo = rt_ap_num(tp, tpo, col); tp[tpo] = 95 as u8; tpo = tpo + 1 // '_' 148 tpo = rt_ap_num(tp, tpo, row); tpo = rt_ap_str(tp, tpo, ".jpg" as *u8); tp[tpo] = 0 as u8 149 if rt_writefile(tp, jout, jlen) > 0 { total = total + 1 } 150 } 151 col = col + 1 152 } 153 row = row + 1 154 } 155 level = level + 1 156 } 157 return total 158}