nx_jpeg_mcu.nx source
↩ module page · 189 lines · 8022 B
1// nx_jpeg_mcu.nx -- baseline-JPEG MCU walker. Per ITU-T Rec. T.81
2// sec A.2.3. The top-level orchestrator that ties together every
3// per-block primitive shipped earlier in Arc C1.
4//
5// MCU = Minimum Coded Unit. For each MCU position in the image:
6// for each component (in scan order):
7// for each data unit (block) in this MCU for this component:
8// (Hi * Vi blocks total for component i)
9// 1. nx_jpeg_ent_decode_block -- entropy decode -> zz-order 64 coeffs
10// 2. nx_jpeg_dequant_un_zigzag -- * QTable, un-zigzag -> 64 nat coeffs
11// 3. nx_jpeg_idct_8x8 -- inverse DCT -> 64 samples
12// 4. level shift +128 + clamp to [0..255] -> 64 u8 samples
13// 5. write to component sample plane at correct (x,y)
14//
15// Per-component plane dimensions (T.81 sec A.2.4):
16// x_i = ceil(X * Hi / max_h)
17// y_i = ceil(Y * Vi / max_v)
18//
19// For 4:2:0 YCbCr (max_h = max_v = 2; Y has H=V=2, Cb/Cr H=V=1):
20// Y plane: full image width x height (each MCU contributes 16x16 px = 4 blocks)
21// Cb plane: half width x half height (each MCU contributes 8x8 px = 1 block)
22// Cr plane: half width x half height (each MCU contributes 8x8 px = 1 block)
23//
24// nx_safety_envelope:
25// intended_use: "Top-level MCU walker for baseline JPEG decode."
26// sil_target: SIL1
27// evidence: [t81_section_a_2_3_canonical_basis,
28// composes_9_per_block_primitives,
29// bounded_iteration_via_sof_mcu_rows_cols]
30// hazard_register: [bug-tape-mcu-grid-overrun,
31// bug-tape-component-plane-stride-mismatch,
32// bug-tape-prev-dc-not-reset-per-scan]
33// residual_risk: "Restart-marker (DRI) interval handling not yet
34// wired -- baseline JPEGs without restart markers
35// decode correctly; those with RST0..RST7 markers
36// need a follow-on stone."
37// verdict: NOT_YET_EVALUATED
38
39import "nx_syscalls.nx"
40import "nx_jpeg_sof.nx"
41import "nx_jpeg_sos.nx"
42import "nx_jpeg_dht.nx"
43import "nx_jpeg_entropy.nx"
44import "nx_jpeg_dequant.nx"
45import "nx_jpeg_zigzag.nx"
46import "nx_jpeg_idct.nx"
47
48const NX_JPEG_MCU_OK: i64 = 0
49const NX_JPEG_MCU_BAD_COMPONENT: i64 = 1 // component count mismatch
50const NX_JPEG_MCU_ENTROPY_FAIL: i64 = 2
51const NX_JPEG_MCU_BAD_QTABLE: i64 = 3
52const NX_JPEG_MCU_RESULT_N: i64 = 4
53
54func nx_jpeg_mcu_result_is_valid(v: i64) -> i64 {
55 if v < 0 { return 0 }
56 if v >= NX_JPEG_MCU_RESULT_N { return 0 }
57 return 1
58}
59
60// Context struct packing IDCT cosine table + 3 scratch i64 buffers.
61// Allocated once per decode + passed by reference to keep the
62// per-block decoder's parameter count under the substrate cap.
63struct NxJpegMcuScratch {
64 cos_tbl: *i64, // 64 i64
65 zz: *i64, // 64 i64 (entropy decoder zig-zag output)
66 natural: *i64, // 64 i64 (after dequant + un-zigzag)
67 samples: *i64 // 64 i64 (after IDCT, pre-clamp)
68}
69
70const NX_JPEG_MCU_SCRATCH_BYTES: i64 = 32
71
72// Initialise a caller-allocated NxJpegMcuScratch with fresh mmap'd
73// buffers + cosine table. Caller is responsible for the scratch
74// struct itself (NX_JPEG_MCU_SCRATCH_BYTES).
75func nx_jpeg_mcu_scratch_init(s: *NxJpegMcuScratch) -> i64 {
76 s.cos_tbl = sys_mmap(64 * 8) as *i64
77 s.zz = sys_mmap(64 * 8) as *i64
78 s.natural = sys_mmap(64 * 8) as *i64
79 s.samples = sys_mmap(64 * 8) as *i64
80 nx_jpeg_idct_init_cos_table(s.cos_tbl)
81 return 0
82}
83
84// Bitstream cursor passed to entropy primitives. Packs byte buffer
85// + per-bit offset + source bound into a single by-ref struct.
86struct NxJpegBitStream {
87 src: *u8,
88 src_end: i64,
89 byte_idx: i64, // current byte position
90 bit_off: i64 // bit offset in current byte (0..7, MSB first)
91}
92
93const NX_JPEG_BITSTREAM_BYTES: i64 = 32
94
95// Process one 8x8 block: entropy decode -> dequant + un-zigzag -> IDCT
96// -> level shift + clamp -> write to plane at (px, py).
97func nx_jpeg_mcu_decode_one_block(dc_table: *NxJpegHTable,
98 ac_table: *NxJpegHTable,
99 qt_zz: *i64,
100 prev_dc_p: *i64,
101 bs: *NxJpegBitStream,
102 plane: *u8, plane_stride: i64,
103 px: i64, py: i64,
104 sc: *NxJpegMcuScratch) -> i64 {
105 let bit_off_p: *i64 = (bs as i64 + 24) as *i64 // & bs.bit_off
106 let byte_idx_p: *i64 = (bs as i64 + 16) as *i64 // & bs.byte_idx
107 let bit_src: *u8 = bs.src
108 let src_end: i64 = bs.src_end
109 let cos_tbl: *i64 = sc.cos_tbl
110 let scratch_zz: *i64 = sc.zz
111 let scratch_nat: *i64 = sc.natural
112 let scratch_samples: *i64 = sc.samples
113 let rc: i64 = nx_jpeg_ent_decode_block(dc_table, ac_table, prev_dc_p,
114 scratch_zz, bit_src,
115 bit_off_p, byte_idx_p, src_end)
116 if rc != NX_JPEG_ENT_OK { return NX_JPEG_MCU_ENTROPY_FAIL }
117 nx_jpeg_dequant_un_zigzag(scratch_zz, qt_zz, scratch_nat)
118 nx_jpeg_idct_8x8(scratch_nat, scratch_samples, cos_tbl)
119
120 // Level shift +128 + clamp + write 8x8 to plane at (px, py).
121 var ry: i64 = 0
122 while ry < 8 {
123 var rx: i64 = 0
124 while rx < 8 {
125 var v: i64 = scratch_samples[ry * 8 + rx] + 128
126 if v < 0 { v = 0 }
127 if v > 255 { v = 255 }
128 let plane_x: i64 = px + rx
129 let plane_y: i64 = py + ry
130 plane[plane_y * plane_stride + plane_x] = v as u8
131 rx = rx + 1
132 }
133 ry = ry + 1
134 }
135 return NX_JPEG_MCU_OK
136}
137
138// MCU position descriptor: tells the walker where to write each block.
139//
140// For 4:2:0 YCbCr the MCU contains 6 blocks in scan order:
141// Y(0,0) Y(1,0) Y(0,1) Y(1,1) Cb(0,0) Cr(0,0)
142// Y blocks fill a 16x16 region of the Y plane; each Cb/Cr block fills
143// 8x8 of its (half-size) plane.
144//
145// The walker is told via `n_blocks` how many blocks per MCU per
146// component (= Hi * Vi). For each block index `k` in 0..Hi*Vi:
147// block_x_in_mcu = k % Hi
148// block_y_in_mcu = k / Hi
149// pixel position in component plane:
150// plane_x = mcu_col * Hi * 8 + block_x_in_mcu * 8
151// plane_y = mcu_row * Vi * 8 + block_y_in_mcu * 8
152
153// Process one component's contribution to one MCU.
154//
155// sof_comp / sos_comp -- per-component descriptors
156// plane / plane_stride
157// mcu_col, mcu_row -- MCU grid coords
158// dc_tables, ac_tables -- caller-resolved Huffman tables for this component
159// qt_zz -- caller-resolved quantization table for this component
160// prev_dc_p -- running prev_dc for this component
161// bit_src, ... -- bitstream cursor (advances across blocks)
162// cos_tbl, scratch_* -- as in nx_jpeg_mcu_decode_one_block
163func nx_jpeg_mcu_decode_one_component(sof_comp: *NxJpegSofComponent,
164 dc_table: *NxJpegHTable,
165 ac_table: *NxJpegHTable,
166 qt_zz: *i64,
167 prev_dc_p: *i64,
168 mcu_col: i64, mcu_row: i64,
169 plane: *u8, plane_stride: i64,
170 bs: *NxJpegBitStream,
171 sc: *NxJpegMcuScratch) -> i64 {
172 let hi: i64 = sof_comp.hi
173 let vi: i64 = sof_comp.vi
174 var v: i64 = 0
175 while v < vi {
176 var h: i64 = 0
177 while h < hi {
178 let px: i64 = mcu_col * hi * 8 + h * 8
179 let py: i64 = mcu_row * vi * 8 + v * 8
180 let rc: i64 = nx_jpeg_mcu_decode_one_block(
181 dc_table, ac_table, qt_zz, prev_dc_p,
182 bs, plane, plane_stride, px, py, sc)
183 if rc != NX_JPEG_MCU_OK { return rc }
184 h = h + 1
185 }
186 v = v + 1
187 }
188 return NX_JPEG_MCU_OK
189}