nx_jpeg_progressive.nx source
↩ module page · 399 lines · 18927 B
1// nx_jpeg_progressive.nx -- PROGRESSIVE JPEG decode (SOF2), the eat-the-debt unblock for real web photos.
2// Baseline decodes one scan block->IDCT->pixel; progressive has MANY scans that each refine a spectral band of
3// a full-image COEFFICIENT buffer (spectral selection Ss..Se + successive approximation Ah/Al), then a single
4// IDCT pass at the end. Four scan kinds: DC-first, DC-refine, AC-first (EOBRUN), AC-refine (the fiddly one).
5// Faithful to T.81 G.1.2 / libjpeg jdphuff. Restart intervals (DRI>0) NOT yet handled (assumes DRI=0). ORIGINAL.
6import "nx_syscalls.nx"
7import "nx_jpeg_sof.nx"
8import "nx_jpeg_sos.nx"
9import "nx_jpeg_dqt.nx"
10import "nx_jpeg_dht.nx"
11import "nx_jpeg_entropy.nx"
12import "nx_jpeg_dequant.nx"
13import "nx_jpeg_idct.nx"
14import "nx_jpeg_mcu.nx"
15import "nx_jpeg_decode.nx"
16
17const NX_JPEG_PROG_OK: i64 = 0
18const NX_JPEG_PROG_FAIL: i64 = 1
19
20func pgw(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(2, s, n); return 0 }
21func pgd(v: i64) -> i64 {
22 let b: *u8 = sys_mmap(32) as *u8
23 b[31] = 32 as u8
24 var x: i64 = v; var neg: i64 = 0; var i: i64 = 30
25 if x < 0 { neg = 1; x = 0 - x }
26 if x == 0 { b[i] = 48 as u8; i = i - 1 }
27 while x > 0 { b[i] = (48 + x % 10) as u8; x = x / 10; i = i - 1 }
28 if neg == 1 { b[i] = 45 as u8; i = i - 1 }
29 sys_write(2, (b as i64 + i + 1) as *u8, 31 - i)
30 return 0
31}
32
33// does this JPEG use progressive (SOF2) coding? scans markers for a SOF; returns 1 for SOF2, 0 otherwise.
34func nx_jpeg_is_progressive(jpeg: *u8, jpeg_len: i64) -> i64 {
35 var p: i64 = 2
36 while p + 3 < jpeg_len {
37 if (jpeg[p] & 0xff) == 0xFF {
38 let m: i64 = jpeg[p + 1] & 0xff
39 if m == 0xC2 { return 1 } // SOF2 progressive
40 if m == 0xC0 { return 0 } // SOF0 baseline
41 if m == 0xC1 { return 0 } // SOF1 extended-sequential
42 if m == 0xD8 { p = p + 2 } else {
43 if m == 0xD9 { return 0 } else {
44 if m == 0x01 { p = p + 2 } else {
45 if m >= 0xD0 { if m <= 0xD7 { p = p + 2 } else { p = p + 2 + prog_be16(jpeg, p + 2) } } else { p = p + 2 + prog_be16(jpeg, p + 2) }
46 }
47 }
48 }
49 } else { p = p + 1 }
50 }
51 return 0
52}
53
54// find the next REAL marker from `from` (skip stuffed FF00, fill FFs, and RSTn which are entropy). Returns the
55// offset of the 0xFF, or len if none.
56func prog_next_marker(j: *u8, len: i64, from: i64) -> i64 {
57 var i: i64 = from
58 while i < len - 1 {
59 if (j[i] & 0xff) == 0xFF {
60 let m: i64 = j[i + 1] & 0xff
61 if m != 0x00 {
62 if m == 0xFF { i = i + 1 } else {
63 if m >= 0xD0 { if m <= 0xD7 { i = i + 2 } else { return i } } else { return i }
64 }
65 } else { i = i + 2 }
66 } else { i = i + 1 }
67 }
68 return len
69}
70// apply one successive-approximation correction bit to an already-nonzero coefficient (AC refine + DC refine).
71func prog_refine_nz(coeffs: *i64, k: i64, p1: i64, bit: i64) -> i64 {
72 let c: i64 = coeffs[k]
73 if c == 0 { return 0 }
74 if bit == 0 { return 0 }
75 if (c & p1) != 0 { return 0 } // correction bit already applied
76 if c > 0 { coeffs[k] = c + p1 } else { coeffs[k] = c - p1 }
77 return 0
78}
79
80// Decode ONE progressive scan into the coefficient buffers. cf[comp] = coeff buffer; bwp[comp]/bhp[comp] =
81// padded block grid (stride bwp); cbw[comp]/cbh[comp] = the component's OWN block extent (non-interleaved AC).
82func prog_scan(j: *u8, len: i64, scan: *NxJpegScan, frame: *NxJpegFrame,
83 cf: *i64, bwp: *i64, cbw: *i64, cbh: *i64, prevdc: *i64,
84 dcslot: *i64, acslot: *i64,
85 byteidx: *i64, ent_start: i64, ent_end: i64,
86 mcus_x: i64, mcus_y: i64) -> i64 {
87 let bo: *i64 = sys_mmap(8) as *i64
88 let bi: *i64 = sys_mmap(8) as *i64
89 bo[0] = 0
90 bi[0] = ent_start
91 let ss: i64 = scan.ss
92 let se: i64 = scan.se
93 let ah: i64 = scan.ah
94 let al: i64 = scan.al
95 let eobrun: *i64 = sys_mmap(8) as *i64
96 eobrun[0] = 0
97 if ss == 0 {
98 // ---- DC scan (interleaved over all scan components, MCU order) ----
99 var my: i64 = 0
100 while my < mcus_y {
101 var mx: i64 = 0
102 while mx < mcus_x {
103 var s: i64 = 0
104 while s < scan.n_components {
105 let sc: *NxJpegSosComponent = (scan.components as i64 + s * NX_JPEG_SOS_COMP_BYTES) as *NxJpegSosComponent
106 // map scan component -> frame component index by csj==ci
107 var fcidx: i64 = 0
108 var fi: i64 = 0
109 while fi < frame.n_components {
110 let fcp: *NxJpegSofComponent = (frame.components as i64 + fi * NX_JPEG_SOF_COMP_BYTES) as *NxJpegSofComponent
111 if fcp.ci == sc.csj { fcidx = fi }
112 fi = fi + 1
113 }
114 let fc: *NxJpegSofComponent = (frame.components as i64 + fcidx * NX_JPEG_SOF_COMP_BYTES) as *NxJpegSofComponent
115 let stride: i64 = bwp[fcidx]
116 let cbuf: i64 = cf[fcidx]
117 var v: i64 = 0
118 while v < fc.vi {
119 var hh: i64 = 0
120 while hh < fc.hi {
121 let bx: i64 = mx * fc.hi + hh
122 let by: i64 = my * fc.vi + v
123 let blk: *i64 = (cbuf + (by * stride + bx) * 64 * 8) as *i64
124 if ah == 0 {
125 let t: i64 = nx_jpeg_huff_decode_symbol((dcslot[sc.td]) as *NxJpegHTable, j, bo, bi, ent_end)
126 if t < 0 { return NX_JPEG_PROG_FAIL }
127 var diff: i64 = 0
128 if t > 0 { diff = nx_jpeg_ent_extend(nx_jpeg_ent_receive(t, j, bo, bi, ent_end), t) }
129 prevdc[fcidx] = prevdc[fcidx] + diff
130 blk[0] = prevdc[fcidx] << al
131 } else {
132 let bit: i64 = nx_jpeg_ent_receive(1, j, bo, bi, ent_end)
133 if bit == 1 { blk[0] = blk[0] | (1 << al) }
134 }
135 hh = hh + 1
136 }
137 v = v + 1
138 }
139 s = s + 1
140 }
141 mx = mx + 1
142 }
143 my = my + 1
144 }
145 byteidx[0] = bi[0]
146 return NX_JPEG_PROG_OK
147 }
148 // ---- AC scan (non-interleaved: exactly one component) ----
149 let sc0: *NxJpegSosComponent = scan.components
150 var fcidx: i64 = 0
151 var fi: i64 = 0
152 while fi < frame.n_components {
153 let fcp: *NxJpegSofComponent = (frame.components as i64 + fi * NX_JPEG_SOF_COMP_BYTES) as *NxJpegSofComponent
154 if fcp.ci == sc0.csj { fcidx = fi }
155 fi = fi + 1
156 }
157 let stride: i64 = bwp[fcidx]
158 let cbuf: i64 = cf[fcidx]
159 let ac: *NxJpegHTable = (acslot[sc0.ta]) as *NxJpegHTable
160 let p1: i64 = 1 << al
161 let m1: i64 = (0 - 1) << al
162 var by: i64 = 0
163 while by < cbh[fcidx] {
164 var bx: i64 = 0
165 while bx < cbw[fcidx] {
166 let blk: *i64 = (cbuf + (by * stride + bx) * 64 * 8) as *i64
167 if ah == 0 {
168 // AC first pass
169 if eobrun[0] > 0 { eobrun[0] = eobrun[0] - 1 } else {
170 var k: i64 = ss
171 var run: i64 = 1
172 while run == 1 {
173 if k > se { run = 0 } else {
174 let rs: i64 = nx_jpeg_huff_decode_symbol(ac, j, bo, bi, ent_end)
175 if rs < 0 { return NX_JPEG_PROG_FAIL }
176 let r: i64 = rs >> 4
177 let sz: i64 = rs & 0x0F
178 if sz == 0 {
179 if r == 15 { k = k + 16 } else {
180 var eb: i64 = 1 << r
181 if r > 0 { eb = eb + nx_jpeg_ent_receive(r, j, bo, bi, ent_end) }
182 eobrun[0] = eb - 1
183 run = 0
184 }
185 } else {
186 k = k + r
187 if k > se { run = 0 } else {
188 blk[k] = nx_jpeg_ent_extend(nx_jpeg_ent_receive(sz, j, bo, bi, ent_end), sz) << al
189 k = k + 1
190 }
191 }
192 }
193 }
194 }
195 } else {
196 // AC refinement pass (T.81 G.1.2.3)
197 var k: i64 = ss
198 if eobrun[0] == 0 {
199 var run: i64 = 1
200 while run == 1 {
201 if k > se { run = 0 } else {
202 let rs: i64 = nx_jpeg_huff_decode_symbol(ac, j, bo, bi, ent_end)
203 if rs < 0 { return NX_JPEG_PROG_FAIL }
204 var r: i64 = rs >> 4
205 let sz: i64 = rs & 0x0F
206 var newval: i64 = 0
207 if sz == 0 {
208 if r != 15 {
209 var eb: i64 = 1 << r
210 if r > 0 { eb = eb + nx_jpeg_ent_receive(r, j, bo, bi, ent_end) }
211 eobrun[0] = eb
212 run = 0
213 }
214 // r==15 (ZRL): skip 16 zero coeffs (with correction of nonzeros between), newval stays 0
215 } else {
216 if nx_jpeg_ent_receive(1, j, bo, bi, ent_end) == 1 { newval = p1 } else { newval = m1 }
217 }
218 if run == 1 {
219 // advance over r ZERO coefficients, applying correction bits to nonzeros along the way
220 var adv: i64 = 1
221 while adv == 1 {
222 if k > se { adv = 0; run = 0 } else {
223 if blk[k] != 0 {
224 prog_refine_nz(blk, k, p1, nx_jpeg_ent_receive(1, j, bo, bi, ent_end))
225 } else {
226 if r == 0 { adv = 0 } else { r = r - 1 }
227 }
228 if adv == 1 { k = k + 1 }
229 }
230 }
231 if run == 1 {
232 if sz != 0 { blk[k] = newval }
233 k = k + 1
234 }
235 }
236 }
237 }
238 }
239 if eobrun[0] > 0 {
240 // correction bits for all remaining nonzero coeffs in [k..se]
241 while k <= se {
242 if blk[k] != 0 { prog_refine_nz(blk, k, p1, nx_jpeg_ent_receive(1, j, bo, bi, ent_end)) }
243 k = k + 1
244 }
245 eobrun[0] = eobrun[0] - 1
246 }
247 }
248 bx = bx + 1
249 }
250 by = by + 1
251 }
252 byteidx[0] = bi[0]
253 return NX_JPEG_PROG_OK
254}
255
256func prog_be16(j: *u8, o: i64) -> i64 { return ((j[o] & 0xff) << 8) | (j[o + 1] & 0xff) }
257
258// TOP-LEVEL progressive decode: walk markers, decode every scan into coeff buffers, then a single IDCT pass
259// into the caller's planes (same layout the baseline back-end + upsample/colour-convert expect). ctx.frame is
260// already parsed (by the SOF probe). planes[c]/strides[c] are the MCU-padded component planes.
261func nx_jpeg_prog_decode(jpeg: *u8, jpeg_len: i64, ctx: *NxJpegDecCtx, planes: *u8, strides: *i64) -> i64 {
262 let frame: *NxJpegFrame = ctx.frame
263 let W: i64 = frame.width
264 let H: i64 = frame.height
265 let maxh: i64 = frame.max_h
266 let maxv: i64 = frame.max_v
267 let mcus_x: i64 = (W + 8 * maxh - 1) / (8 * maxh)
268 let mcus_y: i64 = (H + 8 * maxv - 1) / (8 * maxv)
269 let nf: i64 = frame.n_components
270 let cf: *i64 = sys_mmap(8 * nf) as *i64
271 let bwp: *i64 = sys_mmap(8 * nf) as *i64
272 let bhp: *i64 = sys_mmap(8 * nf) as *i64
273 let cbw: *i64 = sys_mmap(8 * nf) as *i64
274 let cbh: *i64 = sys_mmap(8 * nf) as *i64
275 let prevdc: *i64 = sys_mmap(8 * nf) as *i64
276 var c: i64 = 0
277 while c < nf {
278 let fc: *NxJpegSofComponent = (frame.components as i64 + c * NX_JPEG_SOF_COMP_BYTES) as *NxJpegSofComponent
279 let bw: i64 = mcus_x * fc.hi
280 let bh: i64 = mcus_y * fc.vi
281 let cw: i64 = (W * fc.hi + maxh - 1) / maxh
282 let ch: i64 = (H * fc.vi + maxv - 1) / maxv
283 cf[c] = sys_mmap(bw * bh * 64 * 8) as i64 // mmap zero-fills -> coeffs start at 0
284 bwp[c] = bw
285 bhp[c] = bh
286 cbw[c] = (cw + 7) / 8
287 cbh[c] = (ch + 7) / 8
288 prevdc[c] = 0
289 c = c + 1
290 }
291 let dcslot: *i64 = sys_mmap(8 * 4) as *i64
292 let acslot: *i64 = sys_mmap(8 * 4) as *i64
293 nx_jpeg_ctx_tables_reset(ctx)
294 let scan: *NxJpegScan = ctx.scan
295 let bidx: *i64 = sys_mmap(8) as *i64
296 // ---- manual marker walk (entropy-skipping between scans) ----
297 var p: i64 = 2 // past SOI (FF D8)
298 var walking: i64 = 1
299 while walking == 1 {
300 if p + 1 >= jpeg_len { walking = 0 } else {
301 if (jpeg[p] & 0xff) != 0xFF { p = p + 1 } else {
302 while (jpeg[p + 1] & 0xff) == 0xFF { p = p + 1 } // skip fill FFs
303 let marker: i64 = jpeg[p + 1] & 0xff
304 p = p + 2
305 if marker == 0xD9 { walking = 0 } else { // EOI
306 var standalone: i64 = 0
307 if marker == 0x01 { standalone = 1 } // TEM (no payload)
308 if marker >= 0xD0 { if marker <= 0xD7 { standalone = 1 } } // RSTn (no payload)
309 if standalone == 0 {
310 let seglen: i64 = prog_be16(jpeg, p)
311 let poff: i64 = p + 2
312 let plen: i64 = seglen - 2
313 if marker == 0xDB {
314 // tables are placed by Tq (a redefinition replaces; the keyed area never grows) -- see nx_jpeg_decode.nx
315 if nx_jpeg_ctx_parse_dqt(ctx, (jpeg as i64 + poff) as *u8, plen) != NX_JPEG_DQT_OK { return NX_JPEG_PROG_FAIL }
316 }
317 if marker == 0xC4 {
318 if nx_jpeg_ctx_parse_dht(ctx, (jpeg as i64 + poff) as *u8, plen) != NX_JPEG_DHT_OK { return NX_JPEG_PROG_FAIL }
319 // the scan slots read the KEYED tables, so a table redefined between scans is live for the next scan
320 var ti: i64 = 0
321 while ti < NX_JPEG_TH_PER_CLASS {
322 dcslot[ti] = nx_jpeg_ctx_htable(ctx, 0, ti) as i64
323 acslot[ti] = nx_jpeg_ctx_htable(ctx, 1, ti) as i64
324 ti = ti + 1
325 }
326 }
327 if marker == 0xDA {
328 nx_jpeg_sos_parse((jpeg as i64 + poff) as *u8, plen, scan) // populates ss/se/ah/al
329 let ent_start: i64 = poff + plen
330 let ent_end: i64 = prog_next_marker(jpeg, jpeg_len, ent_start)
331 if scan.ss == 0 { if scan.ah == 0 { var rc2: i64 = 0; while rc2 < nf { prevdc[rc2] = 0; rc2 = rc2 + 1 } } }
332 let psr: i64 = prog_scan(jpeg, jpeg_len, scan, frame, cf, bwp, cbw, cbh, prevdc,
333 dcslot, acslot, bidx, ent_start, ent_end, mcus_x, mcus_y)
334 if psr != NX_JPEG_PROG_OK { return NX_JPEG_PROG_FAIL }
335 p = ent_end
336 } else {
337 p = poff + plen
338 }
339 }
340 }
341 }
342 }
343 }
344 // ---- final: dequant + IDCT every block into the planes ----
345 let sc: *NxJpegMcuScratch = sys_mmap(NX_JPEG_MCU_SCRATCH_BYTES) as *NxJpegMcuScratch
346 nx_jpeg_mcu_scratch_init(sc)
347 c = 0
348 while c < nf {
349 let fc: *NxJpegSofComponent = (frame.components as i64 + c * NX_JPEG_SOF_COMP_BYTES) as *NxJpegSofComponent
350 var qt: *i64 = 0 as *i64
351 var qi: i64 = 0
352 while qi < ctx.n_qtables {
353 let qe: *NxJpegQTable = (ctx.qtables as i64 + qi * NX_JPEG_QTABLE_BYTES) as *NxJpegQTable
354 if qe.tq == fc.tqi { qt = qe.values }
355 qi = qi + 1
356 }
357 if (qt as i64) == 0 { return NX_JPEG_PROG_FAIL }
358 let stride: i64 = bwp[c]
359 let cbuf: i64 = cf[c]
360 let planes_p: *i64 = planes as *i64
361 let pl: *u8 = planes_p[c] as *u8
362 let pstride: i64 = strides[c]
363 var by: i64 = 0
364 while by < bhp[c] {
365 var bx: i64 = 0
366 while bx < bwp[c] {
367 let blk: *i64 = (cbuf + (by * stride + bx) * 64 * 8) as *i64
368 nx_jpeg_dequant_un_zigzag(blk, qt, sc.natural)
369 nx_jpeg_idct_8x8(sc.natural, sc.samples, sc.cos_tbl)
370 var ry: i64 = 0
371 while ry < 8 {
372 var rx: i64 = 0
373 while rx < 8 {
374 var vv: i64 = sc.samples[ry * 8 + rx] + 128
375 if vv < 0 { vv = 0 }
376 if vv > 255 { vv = 255 }
377 pl[(by * 8 + ry) * pstride + (bx * 8 + rx)] = vv as u8
378 rx = rx + 1
379 }
380 ry = ry + 1
381 }
382 bx = bx + 1
383 }
384 by = by + 1
385 }
386 c = c + 1
387 }
388 // Free the per-component coefficient planes (2026-08-23): bwp[c]*bhp[c]*64*8 bytes each --
389 // ~134 MB per full-res component at 4096x4096 -- previously leaked on every progressive
390 // decode. Exact lengths from the recorded block dims. NAMED RESIDUAL: the many early FAIL
391 // exits above still leak theirs; threading frees through each is a larger refactor and the
392 // dominant success path is closed here.
393 var fcp: i64 = 0
394 while fcp < nf {
395 sys_munmap(cf[fcp] as *u8, bwp[fcp] * bhp[fcp] * 64 * 8)
396 fcp = fcp + 1
397 }
398 return NX_JPEG_PROG_OK
399}