code wiki / (root) / nx_manga_page.nx

nx_manga_page.nx source

↩ module page · 264 lines · 12088 B

1// nx_manga_page.nx -- R1 KEYSTONE of the mangagen lane (comparewatch- contracts mangagen_mp_page_emit 2// + mangagen_mp_layout_rows): compose N input images onto ONE manga page PNG from a DATA-DRIVEN layout 3// row. Layouts are rows, never code (house law): 4// layouts.conf line: name|pageW|pageH|border|order|x,y,w,h;x,y,w,h;... 5// panel rects are PER-MILLE of the page; order rtl reverses the input->panel mapping (manga read 6// order); border = black frame thickness px drawn inside each panel rect. 7// FAIL-CLOSED: input count != panel count REFUSES (a page with empty or silently-dropped panels is a 8// wrong artifact wearing a clean filename); unknown layout REFUSES naming the conf and the ask. 9// Partition is PRINTED per the R1 accept rule: every input placed exactly once, panel<-input mapping. 10// usage: nx_manga_page <layouts.conf> <layout> <out.png> <img1> ... <imgN> 11// license_tier: ORIGINAL expect_exit: 0 12import "nx_str.nx" 13import "nx_syscalls.nx" 14import "nx_img_to_rgb.nx" 15import "nx_png_write.nx" 16const MP_MAXP: i64 = 24 17const MP_CONFCAP: i64 = 65536 18func mp_puts(s: *u8) -> i64 { sys_write(1, s, nx_str_len(s)); return 0 } 19func mp_pi(v: i64) -> i64 { 20 let t: *u8 = sys_mmap(32) 21 let o: *u8 = sys_mmap(32) 22 var m: i64 = v 23 var k: i64 = 0 24 if m < 0 { m = 0 - m; sys_write(1, "-" as *u8, 1) } 25 if m == 0 { t[0] = 48; k = 1 } 26 while m > 0 { t[k] = 48 + (m % 10); m = m / 10; k = k + 1 } 27 var i: i64 = 0 28 while i < k { o[i] = t[k - 1 - i]; i = i + 1 } 29 sys_write(1, o, k) 30 return 0 31} 32func mp_read(path: *u8, buf: *u8, cap: i64) -> i64 { 33 let fd: i64 = sys_openat_rd(path); if fd < 0 { return 0 - 1 } 34 var tot: i64 = 0 35 while tot < cap { let r: i64 = sys_read(fd, (buf as i64 + tot) as *u8, cap - tot); if r <= 0 { break } tot = tot + r } 36 sys_close(fd); return tot 37} 38// parse int from buf[a..b) (digits only, ignores others up to first digit run end) 39func mp_int(buf: *u8, a: i64, b: i64) -> i64 { 40 var v: i64 = 0 41 var i: i64 = a 42 var seen: i64 = 0 43 var go: i64 = 1 44 while go == 1 { 45 if i >= b { go = 0 } else { 46 let c: i64 = buf[i] as i64 47 if c >= 48 { if c <= 57 { v = v * 10 + (c - 48); seen = 1; i = i + 1 } else { if seen == 1 { go = 0 } else { i = i + 1 } } } else { if seen == 1 { go = 0 } else { i = i + 1 } } 48 } 49 } 50 return v 51} 52// THE LAYOUT-ROWS CONTRACT (watched as mangagen_mp_layout_rows): find <name> in the conf, fill 53// meta[0..4]=W,H,border,order(0 ltr/1 rtl),npanels and rects[i*4..] per-mille. Returns npanels, or 54// -1 conf-unreadable, -2 layout-not-found, -3 too-many-panels. 55func mp_layout_rows(confpath: *u8, name: *u8, meta: *i64, rects: *i64) -> i64 { 56 let buf: *u8 = sys_mmap(MP_CONFCAP) 57 let n: i64 = mp_read(confpath, buf, MP_CONFCAP) 58 if n <= 0 { return 0 - 1 } 59 var p: i64 = 0 60 while p < n { 61 var e: i64 = p 62 while e < n { if buf[e] == (10 as u8) { break } e = e + 1 } 63 if buf[p] != (35 as u8) { 64 // match name up to first '|' 65 var i: i64 = 0 66 var ok: i64 = 1 67 var stop: i64 = 0 68 while stop == 0 { 69 if p + i >= e { stop = 1 } else { 70 let c: i64 = buf[p + i] as i64 71 if c == 124 { stop = 1 } else { 72 let nc: i64 = name[i] as i64 73 if nc == 0 { ok = 0; stop = 1 } else { if nc != c { ok = 0; stop = 1 } else { i = i + 1 } } 74 } 75 } 76 } 77 if ok == 1 { if (name[i] as i64) == 0 { if i > 0 { 78 // fields: name|W|H|border|order|rects 79 var f: i64 = 0 80 let fp: *i64 = sys_mmap(64) as *i64 81 var x: i64 = p 82 while x < e { if buf[x] == (124 as u8) { if f < 6 { fp[f] = x; f = f + 1 } } x = x + 1 } 83 if f >= 5 { 84 meta[0] = mp_int(buf, fp[0] + 1, fp[1]) 85 meta[1] = mp_int(buf, fp[1] + 1, fp[2]) 86 meta[2] = mp_int(buf, fp[2] + 1, fp[3]) 87 var ord: i64 = 0 88 if buf[fp[3] + 1] == (114 as u8) { ord = 1 } 89 meta[3] = ord 90 var np: i64 = 0 91 var rp: i64 = fp[4] + 1 92 var go2: i64 = 1 93 while go2 == 1 { 94 if rp >= e { go2 = 0 } else { 95 if np >= MP_MAXP { return 0 - 3 } 96 var re: i64 = rp 97 while re < e { if buf[re] == (59 as u8) { break } re = re + 1 } 98 // rect x,y,w,h 99 var cp: i64 = rp 100 var ci: i64 = 0 101 while ci < 4 { 102 var ce: i64 = cp 103 while ce < re { if buf[ce] == (44 as u8) { break } ce = ce + 1 } 104 rects[np * 4 + ci] = mp_int(buf, cp, ce) 105 cp = ce + 1 106 ci = ci + 1 107 } 108 np = np + 1 109 rp = re + 1 110 } 111 } 112 meta[4] = np 113 return np 114 } 115 } } } 116 } 117 p = e + 1 118 } 119 return 0 - 2 120} 121// box-average blit (the cs_draw mechanism, composed not re-invented -- one scaler idiom estate-wide) 122func mp_draw(page: *u8, PW: i64, ox: i64, oy: i64, fw: i64, fh: i64, src: *u8, sw: i64, sh: i64) -> i64 { 123 var y: i64 = 0 124 while y < fh { 125 var x: i64 = 0 126 while x < fw { 127 let sx0: i64 = (x * sw) / fw 128 let sy0: i64 = (y * sh) / fh 129 var sx1: i64 = ((x + 1) * sw) / fw 130 var sy1: i64 = ((y + 1) * sh) / fh 131 if sx1 <= sx0 { sx1 = sx0 + 1 } 132 if sy1 <= sy0 { sy1 = sy0 + 1 } 133 if sx1 > sw { sx1 = sw } 134 if sy1 > sh { sy1 = sh } 135 var r: i64 = 0 136 var g: i64 = 0 137 var b: i64 = 0 138 var cnt: i64 = 0 139 var yy: i64 = sy0 140 while yy < sy1 { 141 var xx: i64 = sx0 142 while xx < sx1 { 143 let o: i64 = (yy * sw + xx) * 3 144 r = r + (src[o] as i64) 145 g = g + (src[o+1] as i64) 146 b = b + (src[o+2] as i64) 147 cnt = cnt + 1 148 xx = xx + 1 149 } 150 yy = yy + 1 151 } 152 if cnt > 0 { 153 let d: i64 = ((oy + y) * PW + (ox + x)) * 3 154 page[d] = (r / cnt) as u8 155 page[d+1] = (g / cnt) as u8 156 page[d+2] = (b / cnt) as u8 157 } 158 x = x + 1 159 } 160 y = y + 1 161 } 162 return 0 163} 164func mp_hline(page: *u8, PW: i64, x0: i64, x1: i64, y: i64) -> i64 { 165 var x: i64 = x0 166 while x < x1 { let d: i64 = (y * PW + x) * 3; page[d] = 16 as u8; page[d+1] = 16 as u8; page[d+2] = 20 as u8; x = x + 1 } 167 return 0 168} 169func mp_vline(page: *u8, PW: i64, x: i64, y0: i64, y1: i64) -> i64 { 170 var y: i64 = y0 171 while y < y1 { let d: i64 = (y * PW + x) * 3; page[d] = 16 as u8; page[d+1] = 16 as u8; page[d+2] = 20 as u8; y = y + 1 } 172 return 0 173} 174// THE PAGE-EMIT CONTRACT (watched as mangagen_mp_page_emit): decode each input, place onto the page 175// per the layout (aspect-fit, centered, white ground; black frame border px inside each rect), print 176// the partition (panel<-input, counts must sum), write the PNG. Returns 0 or a named nonzero. 177func mp_page_emit(meta: *i64, rects: *i64, paths: *i64, n: i64, outp: *u8) -> i64 { 178 let PW: i64 = meta[0] 179 let PH: i64 = meta[1] 180 let bord: i64 = meta[2] 181 let ord: i64 = meta[3] 182 let page: *u8 = sys_mmap(PW * PH * 3 + 64) 183 var p: i64 = 0 184 while p < PW * PH { let d: i64 = p * 3; page[d] = 255 as u8; page[d+1] = 255 as u8; page[d+2] = 255 as u8; p = p + 1 } 185 let wh: *i64 = sys_mmap(32) as *i64 186 var placed: i64 = 0 187 var i: i64 = 0 188 while i < n { 189 var j: i64 = i 190 if ord == 1 { j = n - 1 - i } 191 let path: *u8 = paths[j] as *u8 192 wh[0] = 0 193 wh[1] = 0 194 let rgb: *u8 = nx_img_to_rgb(path, wh) 195 if rgb == 0 as *u8 { mp_puts("MANGA-PAGE-REFUSE undecodable input: " as *u8); mp_puts(path); mp_puts("\n" as *u8); return 4 } 196 if wh[0] <= 0 { return 4 } 197 if wh[1] <= 0 { return 4 } 198 let px: i64 = (rects[i*4+0] * PW) / 1000 199 let py: i64 = (rects[i*4+1] * PH) / 1000 200 let pw2: i64 = (rects[i*4+2] * PW) / 1000 201 let ph2: i64 = (rects[i*4+3] * PH) / 1000 202 let iw: i64 = pw2 - 2 * bord 203 let ih: i64 = ph2 - 2 * bord 204 let sw: i64 = wh[0] 205 let sh: i64 = wh[1] 206 var fw: i64 = iw 207 var fh: i64 = ih 208 if sw * ih >= sh * iw { fh = (iw * sh) / sw } else { fw = (ih * sw) / sh } 209 if fw < 1 { fw = 1 } 210 if fh < 1 { fh = 1 } 211 let ox: i64 = px + bord + (iw - fw) / 2 212 let oy: i64 = py + bord + (ih - fh) / 2 213 mp_draw(page, PW, ox, oy, fw, fh, rgb, sw, sh) 214 // black frame, bord px, inside the rect 215 var t: i64 = 0 216 while t < bord { 217 mp_hline(page, PW, px, px + pw2, py + t) 218 mp_hline(page, PW, px, px + pw2, py + ph2 - 1 - t) 219 mp_vline(page, PW, px + t, py, py + ph2) 220 mp_vline(page, PW, px + pw2 - 1 - t, py, py + ph2) 221 t = t + 1 222 } 223 mp_puts(" panel " as *u8); mp_pi(i); mp_puts(" <- input " as *u8); mp_pi(j) 224 mp_puts(" (" as *u8); mp_puts(path); mp_puts(") " as *u8); mp_pi(fw); mp_puts("x" as *u8); mp_pi(fh); mp_puts("\n" as *u8) 225 placed = placed + 1 226 i = i + 1 227 } 228 let wrc: i64 = nx_png_write_rgb(outp, page, PW, PH) 229 mp_puts(" partition: placed=" as *u8); mp_pi(placed); mp_puts(" of panels=" as *u8); mp_pi(n) 230 if placed == n { mp_puts(" (parts sum)\n" as *u8) } else { mp_puts(" PARTITION-LEAK\n" as *u8); return 5 } 231 if wrc != 0 { mp_puts("MANGA-PAGE-REFUSE png write failed\n" as *u8); return 6 } 232 return 0 233} 234func main(argc: i64, argv: *i64) -> i64 { 235 if argc < 5 { 236 mp_puts("usage: nx_manga_page <layouts.conf> <layout> <out.png> <img1> ... <imgN>\n" as *u8) 237 mp_puts(" layouts are DATA rows: name|pageW|pageH|border|order|x,y,w,h;... (per-mille rects)\n" as *u8) 238 sys_exit(2); return 2 239 } 240 let meta: *i64 = sys_mmap(64) as *i64 241 let rects: *i64 = sys_mmap(MP_MAXP * 4 * 8) as *i64 242 let np: i64 = mp_layout_rows(argv[1] as *u8, argv[2] as *u8, meta, rects) 243 if np == 0 - 1 { mp_puts("MANGA-PAGE-REFUSE conf unreadable: " as *u8); mp_puts(argv[1] as *u8); mp_puts("\n" as *u8); sys_exit(3); return 3 } 244 if np == 0 - 2 { mp_puts("MANGA-PAGE-REFUSE layout not found in conf: " as *u8); mp_puts(argv[2] as *u8); mp_puts(" (add a DATA row, never code)\n" as *u8); sys_exit(3); return 3 } 245 if np <= 0 { mp_puts("MANGA-PAGE-REFUSE bad layout row\n" as *u8); sys_exit(3); return 3 } 246 let nin: i64 = argc - 4 247 if nin != np { 248 mp_puts("MANGA-PAGE-REFUSE input count " as *u8); mp_pi(nin) 249 mp_puts(" != layout panels " as *u8); mp_pi(np) 250 mp_puts(" -- a page with empty or dropped panels is a wrong artifact; supply exactly " as *u8); mp_pi(np); mp_puts(" images\n" as *u8) 251 sys_exit(3); return 3 252 } 253 let paths: *i64 = sys_mmap(MP_MAXP * 8 + 16) as *i64 254 var k: i64 = 0 255 while k < nin { paths[k] = argv[4 + k]; k = k + 1 } 256 mp_puts("=== nx_manga_page layout=" as *u8); mp_puts(argv[2] as *u8) 257 mp_puts(" page=" as *u8); mp_pi(meta[0]); mp_puts("x" as *u8); mp_pi(meta[1]) 258 mp_puts(" panels=" as *u8); mp_pi(np) 259 if meta[3] == 1 { mp_puts(" order=rtl\n" as *u8) } else { mp_puts(" order=ltr\n" as *u8) } 260 let rc: i64 = mp_page_emit(meta, rects, paths, np, argv[3] as *u8) 261 if rc == 0 { mp_puts("MANGA-PAGE-OK " as *u8); mp_puts(argv[3] as *u8); mp_puts("\n" as *u8); sys_exit(0); return 0 } 262 sys_exit(rc) 263 return rc 264}