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}