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}