code wiki / (root) / nx_jpeg_mcu.nx

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}