code wiki / (root) / nx_jpeg_decode.nx

nx_jpeg_decode.nx source

↩ module page · 214 lines · 9941 B

1// nx_jpeg_decode.nx -- top-level baseline-JPEG decoder. The 2// marker-walking front-end that ties every brick in the JPEG arc 3// into a bytes-in / sample-planes-out API. 4// 5// Pipeline: 6// jpeg_bytes 7// -> nx_jpeg_seg_next (marker scan) 8// for each segment: 9// DQT -> nx_jpeg_dqt_parse, append to qtables 10// DHT -> nx_jpeg_dht_parse, append to htables 11// SOF0 -> nx_jpeg_sof_parse, store frame 12// SOS -> nx_jpeg_sos_parse, store scan + record entropy_off 13// (entropy bytes run from segment end to EOI) 14// else -> skip (APP / COM / etc.) 15// -> nx_jpeg_decode_image_resolve (per-component table lookup) 16// -> nx_jpeg_decode_image_walk_mcus (MCU grid driver) 17// 18// Output: per-component sample planes filled. Caller upsamples 19// + YCbCr->RGB to produce the final RGB framebuffer. 20// 21// Caller supplies all the storage (frame + scan + qtables + htables 22// + per-component planes). Substrate adds no malloc beyond the 23// scratch arenas internal to per-block primitives. 24// 25// nx_safety_envelope: 26// intended_use: "Top-level baseline-JPEG decode -- bytes-in, 27// sample-planes-out. Last brick before 28// YCbCr->RGB upsample step." 29// sil_target: SIL1 30// evidence: [composes_marker_dqt_dht_sof_sos_decode_image, 31// sealed_verdicts_per_segment_kind, 32// bounded_iteration] 33// hazard_register: [bug-tape-progressive-mistaken-baseline, 34// bug-tape-restart-marker-not-honored, 35// bug-tape-app-segment-length-overrun] 36// residual_risk: "Baseline (SOF0) only. Progressive (SOF2), 37// extended-sequential (SOF1), and arithmetic- 38// coded variants reject with NOT_BASELINE." 39// verdict: NOT_YET_EVALUATED 40 41import "nx_syscalls.nx" 42import "nx_jpeg_marker.nx" 43import "nx_jpeg_dqt.nx" 44import "nx_jpeg_dht.nx" 45import "nx_jpeg_sof.nx" 46import "nx_jpeg_sos.nx" 47import "nx_jpeg_mcu.nx" 48import "nx_jpeg_decode_image.nx" 49 50const NX_JPEG_DEC_TOP_OK: i64 = 0 51const NX_JPEG_DEC_TOP_NO_SOI: i64 = 1 52const NX_JPEG_DEC_TOP_NO_SOF: i64 = 2 53const NX_JPEG_DEC_TOP_NO_SOS: i64 = 3 54const NX_JPEG_DEC_TOP_BAD_DQT: i64 = 4 55const NX_JPEG_DEC_TOP_BAD_DHT: i64 = 5 56const NX_JPEG_DEC_TOP_BAD_SOF: i64 = 6 57const NX_JPEG_DEC_TOP_BAD_SOS: i64 = 7 58const NX_JPEG_DEC_TOP_NOT_BASELINE: i64 = 8 59const NX_JPEG_DEC_TOP_IMAGE_FAIL: i64 = 9 // resolve / walk_mcus error 60const NX_JPEG_DEC_TOP_RESULT_N: i64 = 10 61 62func nx_jpeg_dec_top_result_is_valid(v: i64) -> i64 { 63 if v < 0 { return 0 } 64 if v >= NX_JPEG_DEC_TOP_RESULT_N { return 0 } 65 return 1 66} 67 68// Caller-supplied storage for the decoder's intermediate state. 69// Allows the decoder to be invoked with no internal allocation. 70struct NxJpegDecCtx { 71 frame: *NxJpegFrame, 72 scan: *NxJpegScan, 73 sof_comps: *NxJpegSofComponent, // 4 slots 74 sos_comps: *NxJpegSosComponent, // 4 slots 75 qtables: *NxJpegQTable, // 4 slots 76 qvalue_bufs: *i64, // 4 * 64 i64 77 htables: *NxJpegHTable, // 4 slots 78 hbits_pool: *i64, // 4 * 17 i64 79 hhuffval_pool: *i64, // 4 * 256 i64 80 hmincode_pool: *i64, // 4 * 17 i64 81 hmaxcode_pool: *i64, // 4 * 17 i64 82 hvalptr_pool: *i64, // 4 * 17 i64 83 cstates: *NxJpegDecCompState, // 4 slots 84 n_qtables: i64, 85 n_htables: i64 86} 87 88const NX_JPEG_DEC_CTX_BYTES: i64 = 112 89 90// Allocate every working buffer the decoder needs. Caller still 91// owns the planes + strides arrays + the jpeg byte buffer. 92func nx_jpeg_dec_ctx_init(ctx: *NxJpegDecCtx) -> i64 { 93 ctx.frame = sys_mmap(NX_JPEG_FRAME_BYTES) as *NxJpegFrame 94 ctx.scan = sys_mmap(NX_JPEG_SCAN_BYTES) as *NxJpegScan 95 ctx.sof_comps = sys_mmap(NX_JPEG_SOF_COMP_BYTES * 4) as *NxJpegSofComponent 96 ctx.sos_comps = sys_mmap(NX_JPEG_SOS_COMP_BYTES * 4) as *NxJpegSosComponent 97 ctx.qtables = sys_mmap(NX_JPEG_QTABLE_BYTES * 4) as *NxJpegQTable 98 ctx.qvalue_bufs = sys_mmap(4 * 64 * 8) as *i64 99 ctx.htables = sys_mmap(NX_JPEG_HTABLE_BYTES * 4) as *NxJpegHTable 100 ctx.hbits_pool = sys_mmap(4 * 17 * 8) as *i64 101 ctx.hhuffval_pool = sys_mmap(4 * 256 * 8) as *i64 102 ctx.hmincode_pool = sys_mmap(4 * 17 * 8) as *i64 103 ctx.hmaxcode_pool = sys_mmap(4 * 17 * 8) as *i64 104 ctx.hvalptr_pool = sys_mmap(4 * 17 * 8) as *i64 105 ctx.cstates = sys_mmap(NX_JPEG_DEC_COMPSTATE_BYTES * 4) as *NxJpegDecCompState 106 ctx.frame.components = ctx.sof_comps 107 ctx.scan.components = ctx.sos_comps 108 ctx.n_qtables = 0 109 ctx.n_htables = 0 110 return 0 111} 112 113// Top-level decode: walks markers in `jpeg`, gathers metadata, 114// then drives the MCU walker to fill per-component sample planes. 115// 116// jpeg, jpeg_len -- the full JPEG byte buffer 117// ctx -- caller-allocated working state 118// planes -- packed array of *u8 (per scan component) 119// strides -- packed array of i64 (per scan component) 120// 121// Returns NX_JPEG_DEC_TOP_OK on success. 122func nx_jpeg_decode(jpeg: *u8, jpeg_len: i64, 123 ctx: *NxJpegDecCtx, 124 planes: *u8, strides: *i64) -> i64 { 125 let cur: *NxJpegCursor = sys_mmap(NX_JPEG_CURSOR_BYTES) as *NxJpegCursor 126 nx_jpeg_seg_init(cur, jpeg, jpeg_len) 127 let seg: *NxJpegSegment = sys_mmap(NX_JPEG_SEG_BYTES) as *NxJpegSegment 128 129 var saw_soi: i64 = 0 130 var saw_sof: i64 = 0 131 var saw_sos: i64 = 0 132 var entropy_off: i64 = 0 133 var restart_interval: i64 = 0 134 135 let q_temp_count: *i64 = sys_mmap(8) as *i64 136 let h_temp_count: *i64 = sys_mmap(8) as *i64 137 138 var walking: i64 = 1 139 while walking == 1 { 140 let rc: i64 = nx_jpeg_seg_next(cur, seg) 141 if rc == NX_JPEG_SEG_EOF { walking = 0 } 142 if rc != NX_JPEG_SEG_OK { if rc != NX_JPEG_SEG_EOF { walking = 0 } } 143 if walking == 1 { 144 if seg.kind == NX_JPEG_M_SOI { saw_soi = 1 } 145 if seg.kind == NX_JPEG_M_EOI { walking = 0 } 146 if seg.kind == NX_JPEG_M_SOF0 { 147 let sof_rc: i64 = nx_jpeg_sof_parse(jpeg + seg.payload_off, seg.payload_len, ctx.frame) 148 if sof_rc != NX_JPEG_SOF_OK { return NX_JPEG_DEC_TOP_BAD_SOF } 149 saw_sof = 1 150 } 151 if seg.kind == NX_JPEG_M_SOF2 { return NX_JPEG_DEC_TOP_NOT_BASELINE } 152 if seg.kind == NX_JPEG_M_SOF_OTHER { return NX_JPEG_DEC_TOP_NOT_BASELINE } 153 if seg.kind == NX_JPEG_M_DQT { 154 let qrc: i64 = nx_jpeg_dqt_parse( 155 jpeg + seg.payload_off, seg.payload_len, 156 (ctx.qtables as i64 + ctx.n_qtables * NX_JPEG_QTABLE_BYTES) as *NxJpegQTable, 157 (ctx.qvalue_bufs as i64 + ctx.n_qtables * 64 * 8) as *i64, 158 q_temp_count) 159 if qrc != NX_JPEG_DQT_OK { return NX_JPEG_DEC_TOP_BAD_DQT } 160 ctx.n_qtables = ctx.n_qtables + q_temp_count[0] 161 } 162 if seg.kind == NX_JPEG_M_DHT { 163 let hrc: i64 = nx_jpeg_dht_parse( 164 jpeg + seg.payload_off, seg.payload_len, 165 (ctx.htables as i64 + ctx.n_htables * NX_JPEG_HTABLE_BYTES) as *NxJpegHTable, 166 (ctx.hbits_pool as i64 + ctx.n_htables * 17 * 8) as *i64, 167 (ctx.hhuffval_pool as i64 + ctx.n_htables * 256 * 8) as *i64, 168 (ctx.hmincode_pool as i64 + ctx.n_htables * 17 * 8) as *i64, 169 (ctx.hmaxcode_pool as i64 + ctx.n_htables * 17 * 8) as *i64, 170 (ctx.hvalptr_pool as i64 + ctx.n_htables * 17 * 8) as *i64, 171 h_temp_count) 172 if hrc != NX_JPEG_DHT_OK { return NX_JPEG_DEC_TOP_BAD_DHT } 173 ctx.n_htables = ctx.n_htables + h_temp_count[0] 174 } 175 if seg.kind == NX_JPEG_M_DRI { 176 // DRI payload = u16 BE restart interval in MCUs (T.81 B.2.4.4); 0 disables. 177 if seg.payload_len >= 2 { 178 restart_interval = ((jpeg[seg.payload_off] as i64) << 8) | (jpeg[seg.payload_off + 1] as i64) 179 } 180 } 181 if seg.kind == NX_JPEG_M_SOS { 182 let srs: i64 = nx_jpeg_sos_parse(jpeg + seg.payload_off, seg.payload_len, ctx.scan) 183 if srs == NX_JPEG_SOS_NOT_BASELINE { return NX_JPEG_DEC_TOP_NOT_BASELINE } 184 if srs != NX_JPEG_SOS_OK { return NX_JPEG_DEC_TOP_BAD_SOS } 185 entropy_off = seg.payload_off + seg.payload_len 186 saw_sos = 1 187 walking = 0 // entropy stream starts here; stop marker walk 188 } 189 } 190 } 191 192 if saw_soi == 0 { return NX_JPEG_DEC_TOP_NO_SOI } 193 if saw_sof == 0 { return NX_JPEG_DEC_TOP_NO_SOF } 194 if saw_sos == 0 { return NX_JPEG_DEC_TOP_NO_SOS } 195 196 let rrc: i64 = nx_jpeg_decode_image_resolve(ctx.frame, ctx.scan, 197 ctx.qtables, ctx.n_qtables, 198 ctx.htables, ctx.n_htables, 199 planes, strides, ctx.cstates) 200 if rrc != NX_JPEG_DEC_OK { return NX_JPEG_DEC_TOP_IMAGE_FAIL } 201 202 let bs: *NxJpegBitStream = sys_mmap(NX_JPEG_BITSTREAM_BYTES) as *NxJpegBitStream 203 bs.src = jpeg 204 bs.src_end = jpeg_len 205 bs.byte_idx = entropy_off 206 bs.bit_off = 0 207 208 let sc: *NxJpegMcuScratch = sys_mmap(NX_JPEG_MCU_SCRATCH_BYTES) as *NxJpegMcuScratch 209 nx_jpeg_mcu_scratch_init(sc) 210 211 let wrc: i64 = nx_jpeg_decode_image_walk_mcus_ri(ctx.frame, ctx.scan, ctx.cstates, bs, sc, restart_interval) 212 if wrc != NX_JPEG_DEC_OK { return NX_JPEG_DEC_TOP_IMAGE_FAIL } 213 return NX_JPEG_DEC_TOP_OK 214}