code wiki / (root) / nx_jpeg_decode_image.nx

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}