nx_jpeg_decode_image.nx source
↩ module page · 234 lines · 10308 B
1// nx_jpeg_decode_image.nx -- full-image driver for the bits-up
2// baseline JPEG decoder. Per ITU-T Rec. T.81 sec A + F.
3//
4// Takes:
5// - Pre-parsed frame descriptor (NxJpegFrame from nx_jpeg_sof_parse)
6// - Pre-parsed scan header (NxJpegScan from nx_jpeg_sos_parse)
7// - 4-entry quantization tables array (indexed by sof_component.tqi)
8// - 4-entry DC + AC Huffman tables (indexed by scan_component.td/ta)
9// - Entropy-coded byte buffer (the bytes after the SOS header)
10// - Per-component caller-allocated sample planes + their strides
11//
12// Loops over the MCU grid (rows x cols from the frame's max-h/max-v).
13// For each MCU, iterates scan components in order, dispatching to
14// nx_jpeg_mcu_decode_one_component which handles the Hi*Vi block grid.
15//
16// Per-component prev_dc tracking: each scan starts with prev_dc = 0
17// for every component; the MCU walker updates it across blocks.
18//
19// nx_safety_envelope:
20// intended_use: "Top-level baseline-JPEG image decoder, sample
21// planes out. Caller adds YCbCr->RGB upsample."
22// sil_target: SIL1
23// evidence: [t81_section_a_canonical_basis,
24// composes_marker_dqt_dht_sof_sos_entropy_mcu]
25// hazard_register: [bug-tape-component-id-vs-array-index-confusion,
26// bug-tape-restart-interval-not-honored,
27// bug-tape-mcu-grid-cell-stride-mismatch]
28// residual_risk: "Restart-marker (DRI / RSTn) intervals NOT yet
29// honored. Bitstream-with-restart-markers
30// decode requires a follow-on stone."
31// verdict: NOT_YET_EVALUATED
32
33import "nx_syscalls.nx"
34import "nx_jpeg_sof.nx"
35import "nx_jpeg_sos.nx"
36import "nx_jpeg_dht.nx"
37import "nx_jpeg_dequant.nx"
38import "nx_jpeg_mcu.nx"
39
40const NX_JPEG_DEC_OK: i64 = 0
41const NX_JPEG_DEC_BAD_COMP: i64 = 1 // scan component selector doesn't match SOF
42const NX_JPEG_DEC_BAD_TQ: i64 = 2 // SOF component points at unknown qtable
43const NX_JPEG_DEC_BAD_TD: i64 = 3 // scan component DC table id not present
44const NX_JPEG_DEC_BAD_TA: i64 = 4 // scan component AC table id not present
45const NX_JPEG_DEC_MCU_FAIL: i64 = 5 // MCU decode propagated an entropy / mcu error
46const NX_JPEG_DEC_RESULT_N: i64 = 6
47
48func nx_jpeg_dec_result_is_valid(v: i64) -> i64 {
49 if v < 0 { return 0 }
50 if v >= NX_JPEG_DEC_RESULT_N { return 0 }
51 return 1
52}
53
54// Look up the SOF-side component descriptor matching the scan
55// component's selector. Returns *NxJpegSofComponent or null if no
56// match. Linear search -- baseline JPEG has Nf <= 4.
57func _decoder_sof_for_scan_comp(frame: *NxJpegFrame,
58 csj: i64) -> *NxJpegSofComponent {
59 var i: i64 = 0
60 while i < frame.n_components {
61 let comp: *NxJpegSofComponent = (frame.components as i64 + i * NX_JPEG_SOF_COMP_BYTES) as *NxJpegSofComponent
62 if comp.ci == csj { return comp }
63 i = i + 1
64 }
65 return 0 as *NxJpegSofComponent
66}
67
68// Look up a Huffman table by tc + th from a caller-supplied table
69// array (output of nx_jpeg_dht_parse). Returns *NxJpegHTable or null.
70func _decoder_lookup_huffman(tables: *NxJpegHTable, n_tables: i64,
71 want_tc: i64, want_th: i64) -> *NxJpegHTable {
72 var i: i64 = 0
73 while i < n_tables {
74 let t: *NxJpegHTable = (tables as i64 + i * NX_JPEG_HTABLE_BYTES) as *NxJpegHTable
75 if t.tc == want_tc {
76 if t.th == want_th { return t }
77 }
78 i = i + 1
79 }
80 return 0 as *NxJpegHTable
81}
82
83// Per-scan-component working state.
84struct NxJpegDecCompState {
85 sof_comp: *NxJpegSofComponent,
86 dc_table: *NxJpegHTable,
87 ac_table: *NxJpegHTable,
88 qt_zz: *i64, // 64 i64
89 prev_dc: i64, // running DC accumulator
90 plane: *u8,
91 plane_stride: i64
92}
93
94const NX_JPEG_DEC_COMPSTATE_BYTES: i64 = 56
95
96// Resolve every scan component's tables + plane. Returns
97// NX_JPEG_DEC_OK or an error code identifying the first bad
98// component.
99func nx_jpeg_decode_image_resolve(frame: *NxJpegFrame, scan: *NxJpegScan,
100 qtables: *NxJpegQTable, n_qtables: i64,
101 htables: *NxJpegHTable, n_htables: i64,
102 planes: *u8, strides: *i64,
103 state: *NxJpegDecCompState) -> i64 {
104 var j: i64 = 0
105 while j < scan.n_components {
106 let sos_c: *NxJpegSosComponent = (scan.components as i64 + j * NX_JPEG_SOS_COMP_BYTES) as *NxJpegSosComponent
107 let sof_c: *NxJpegSofComponent = _decoder_sof_for_scan_comp(frame, sos_c.csj)
108 if (sof_c as i64) == 0 { return NX_JPEG_DEC_BAD_COMP }
109 // Look up the qtable by tqi.
110 var qt: *i64 = 0 as *i64
111 var qi: i64 = 0
112 while qi < n_qtables {
113 let qe: *NxJpegQTable = (qtables as i64 + qi * NX_JPEG_QTABLE_BYTES) as *NxJpegQTable
114 if qe.tq == sof_c.tqi { qt = qe.values; qi = n_qtables }
115 else { qi = qi + 1 }
116 }
117 if (qt as i64) == 0 { return NX_JPEG_DEC_BAD_TQ }
118
119 let dc_t: *NxJpegHTable = _decoder_lookup_huffman(htables, n_htables, 0, sos_c.td)
120 if (dc_t as i64) == 0 { return NX_JPEG_DEC_BAD_TD }
121 let ac_t: *NxJpegHTable = _decoder_lookup_huffman(htables, n_htables, 1, sos_c.ta)
122 if (ac_t as i64) == 0 { return NX_JPEG_DEC_BAD_TA }
123
124 let s: *NxJpegDecCompState = (state as i64 + j * NX_JPEG_DEC_COMPSTATE_BYTES) as *NxJpegDecCompState
125 s.sof_comp = sof_c
126 s.dc_table = dc_t
127 s.ac_table = ac_t
128 s.qt_zz = qt
129 s.prev_dc = 0
130 // Per-component plane pointer. Caller's `planes` is a packed
131 // array of *u8 pointers (one per scan component, in scan order).
132 let planes_p: *i64 = planes as *i64
133 s.plane = planes_p[j] as *u8
134 s.plane_stride = strides[j]
135 j = j + 1
136 }
137 return NX_JPEG_DEC_OK
138}
139
140// Drive the MCU grid. Caller has pre-resolved component states
141// via nx_jpeg_decode_image_resolve. bs is the entropy-coded
142// byte cursor; sc is the per-block scratch context.
143func nx_jpeg_decode_image_walk_mcus(frame: *NxJpegFrame, scan: *NxJpegScan,
144 state: *NxJpegDecCompState,
145 bs: *NxJpegBitStream,
146 sc: *NxJpegMcuScratch) -> i64 {
147 return nx_jpeg_decode_image_walk_mcus_ri(frame, scan, state, bs, sc, 0)
148}
149
150// Consume one restart marker at an interval boundary (T.81 sec F.2.1.3.1):
151// byte-align the bit cursor, step over FF fill bytes, verify RST0..RST7,
152// advance past it, and reset every scan component's DC predictor. Returns
153// NX_JPEG_DEC_MCU_FAIL when the bytes where a marker MUST sit are anything
154// else -- continuing past a missing marker is a desynced decode that lies
155// with confidence.
156func _decoder_consume_rst(scan: *NxJpegScan, state: *NxJpegDecCompState,
157 bs: *NxJpegBitStream) -> i64 {
158 if bs.bit_off != 0 { bs.bit_off = 0; bs.byte_idx = bs.byte_idx + 1 }
159 let src: *u8 = bs.src
160 let send: i64 = bs.src_end
161 var idx: i64 = bs.byte_idx
162 // If the interval's final padded byte was a data 0xFF, its mandatory stuffing
163 // 0x00 sits between us and the marker -- step over it (at most one; the strict
164 // FF+RSTn check below still gates everything that follows).
165 if idx < send { if (src[idx] as i64) == 0x00 { idx = idx + 1 } }
166 var guard: i64 = 0
167 while guard < 8 {
168 if idx + 1 >= send { return NX_JPEG_DEC_MCU_FAIL }
169 let b0: i64 = src[idx] as i64
170 if b0 != 0xFF { return NX_JPEG_DEC_MCU_FAIL }
171 let b1: i64 = src[idx + 1] as i64
172 if b1 == 0xFF { idx = idx + 1 } else {
173 if b1 >= 0xD0 { if b1 <= 0xD7 {
174 bs.byte_idx = idx + 2
175 bs.bit_off = 0
176 var j: i64 = 0
177 while j < scan.n_components {
178 let s: *NxJpegDecCompState = (state as i64 + j * NX_JPEG_DEC_COMPSTATE_BYTES) as *NxJpegDecCompState
179 s.prev_dc = 0
180 j = j + 1
181 }
182 return NX_JPEG_DEC_OK
183 } }
184 return NX_JPEG_DEC_MCU_FAIL
185 }
186 guard = guard + 1
187 }
188 return NX_JPEG_DEC_MCU_FAIL
189}
190
191// Restart-aware MCU grid driver. ri > 0: every ri MCUs the entropy stream
192// carries a byte-aligned RSTn marker and all DC predictors reset (T.81 sec
193// F.2.1.3.1). ri == 0 is exactly the historic no-restart walk.
194func nx_jpeg_decode_image_walk_mcus_ri(frame: *NxJpegFrame, scan: *NxJpegScan,
195 state: *NxJpegDecCompState,
196 bs: *NxJpegBitStream,
197 sc: *NxJpegMcuScratch, ri: i64) -> i64 {
198 let mcu_cols: i64 = nx_jpeg_sof_mcu_cols(frame)
199 let mcu_rows: i64 = nx_jpeg_sof_mcu_rows(frame)
200 let mcu_total: i64 = mcu_rows * mcu_cols
201 var mcus_done: i64 = 0
202 var ri_left: i64 = ri
203 var my: i64 = 0
204 while my < mcu_rows {
205 var mx: i64 = 0
206 while mx < mcu_cols {
207 var ci: i64 = 0
208 while ci < scan.n_components {
209 let s: *NxJpegDecCompState = (state as i64 + ci * NX_JPEG_DEC_COMPSTATE_BYTES) as *NxJpegDecCompState
210 let prev_dc_p: *i64 = (s as i64 + 32) as *i64 // & s.prev_dc
211 let rc: i64 = nx_jpeg_mcu_decode_one_component(
212 s.sof_comp, s.dc_table, s.ac_table, s.qt_zz,
213 prev_dc_p, mx, my,
214 s.plane, s.plane_stride, bs, sc)
215 if rc != NX_JPEG_MCU_OK { return NX_JPEG_DEC_MCU_FAIL }
216 ci = ci + 1
217 }
218 mcus_done = mcus_done + 1
219 if ri > 0 {
220 ri_left = ri_left - 1
221 if ri_left == 0 {
222 if mcus_done < mcu_total {
223 let rmrc: i64 = _decoder_consume_rst(scan, state, bs)
224 if rmrc != NX_JPEG_DEC_OK { return rmrc }
225 }
226 ri_left = ri
227 }
228 }
229 mx = mx + 1
230 }
231 my = my + 1
232 }
233 return NX_JPEG_DEC_OK
234}