code wiki / (root) / nx_jpeg_sof.nx

nx_jpeg_sof.nx source

↩ module page · 166 lines · 6561 B

1// nx_jpeg_sof.nx -- JPEG Start-of-Frame parser. Per ITU-T Rec. 2// T.81 sec B.2.2 -- the SOF segment that establishes the image's 3// dimensions, sample precision, and component descriptors. 4// 5// Baseline-DCT SOF0 (marker 0xC0) payload layout: 6// 7// Lf (2 bytes BE) -- segment length (already consumed by marker 8// scanner; payload arrives without it) 9// P (1 byte) -- sample precision (always 8 for baseline) 10// Y (2 bytes BE) -- image height in pixels (rows) 11// X (2 bytes BE) -- image width in pixels (cols) 12// Nf (1 byte) -- component count (1=gray, 3=YCbCr, 4=CMYK) 13// per component (3 bytes each): 14// Ci (1 byte) -- component identifier (1..255) 15// HiVi (1 byte) -- high4 = Hi sampling factor, low4 = Vi sampling factor 16// Tqi (1 byte) -- quantization-table id (0..3) per T.81 sec B.2.2 17// 18// Sampling factors are CRITICAL for chroma upsampling: e.g. 19// 4:2:0 has Y component with H=2, V=2 and Cb/Cr with H=1, V=1 20// (luma sampled twice as densely in both axes). 21// 22// Progressive (SOF2 / 0xC2) payload format is identical to SOF0. 23// Extended-sequential (SOF1 / 0xC1) is also identical. Only the 24// scan-direction differs, which this parser doesn't care about. 25// Lossless (SOF3 / 0xC3) and arithmetic-coded variants differ 26// slightly -- not supported here. 27// 28// nx_safety_envelope: 29// intended_use: "Frame-descriptor parsing for JPEG decode." 30// sil_target: SIL1 31// evidence: [t81_section_b_2_2_canonical_basis, 32// sealed_precision_enum, 33// bounded_4_component_max] 34// hazard_register: [bug-tape-sof-component-count-overflow, 35// bug-tape-sof-malformed-precision, 36// bug-tape-sof-zero-dimensions] 37// residual_risk: "Caller must verify decoded Hi/Vi are in 38// [1..4] before using as MCU sampling factor." 39// verdict: NOT_YET_EVALUATED 40 41import "nx_syscalls.nx" 42 43const NX_JPEG_SOF_OK: i64 = 0 44const NX_JPEG_SOF_TRUNC: i64 = 1 45const NX_JPEG_SOF_BAD_PRECISION: i64 = 2 // P != 8 for baseline 46const NX_JPEG_SOF_BAD_NF: i64 = 3 // Nf == 0 or > 4 47const NX_JPEG_SOF_ZERO_DIM: i64 = 4 // X == 0 or Y == 0 48const NX_JPEG_SOF_BAD_SAMPLING: i64 = 5 // Hi or Vi 0 or > 4 49const NX_JPEG_SOF_BAD_TQ: i64 = 6 // Tqi > 3 50const NX_JPEG_SOF_RESULT_N: i64 = 7 51 52func nx_jpeg_sof_result_is_valid(v: i64) -> i64 { 53 if v < 0 { return 0 } 54 if v >= NX_JPEG_SOF_RESULT_N { return 0 } 55 return 1 56} 57 58// One component descriptor. 59struct NxJpegSofComponent { 60 ci: i64, // component identifier (1..255) 61 hi: i64, // horizontal sampling factor (1..4) 62 vi: i64, // vertical sampling factor (1..4) 63 tqi: i64 // quantization-table id (0..3) 64} 65 66const NX_JPEG_SOF_COMP_BYTES: i64 = 32 67 68// Frame descriptor. 69struct NxJpegFrame { 70 precision: i64, // sample bit-depth (8 for baseline) 71 height: i64, // image rows 72 width: i64, // image cols 73 n_components: i64, // 1, 3, or 4 74 components: *NxJpegSofComponent, 75 max_h: i64, // max horizontal sampling factor across components 76 max_v: i64 // max vertical sampling factor 77} 78 79const NX_JPEG_FRAME_BYTES: i64 = 56 80 81// Parse a SOF0 / SOF1 / SOF2 payload into the caller-supplied frame 82// struct. Caller must also supply a 4-entry NxJpegSofComponent 83// array via frame.components (max 4 components in baseline JPEG). 84// 85// payload -- bytes of the SOF segment payload (excluding the 86// 2-byte length prefix that marker scan already consumed) 87// payload_len -- payload byte count 88// frame -- output: caller-allocated NxJpegFrame 89// (frame.components must point to caller-allocated 90// array of 4 NxJpegSofComponent slots) 91func nx_jpeg_sof_parse(payload: *u8, payload_len: i64, 92 frame: *NxJpegFrame) -> i64 { 93 if payload_len < 6 { return NX_JPEG_SOF_TRUNC } 94 let p_bits: i64 = payload[0] as i64 95 if p_bits != 8 { return NX_JPEG_SOF_BAD_PRECISION } 96 let y_hi: i64 = payload[1] as i64 97 let y_lo: i64 = payload[2] as i64 98 let x_hi: i64 = payload[3] as i64 99 let x_lo: i64 = payload[4] as i64 100 let height: i64 = (y_hi << 8) | y_lo 101 let width: i64 = (x_hi << 8) | x_lo 102 let nf: i64 = payload[5] as i64 103 if nf == 0 { return NX_JPEG_SOF_BAD_NF } 104 if nf > 4 { return NX_JPEG_SOF_BAD_NF } 105 if width == 0 { return NX_JPEG_SOF_ZERO_DIM } 106 if height == 0 { return NX_JPEG_SOF_ZERO_DIM } 107 if 6 + nf * 3 > payload_len { return NX_JPEG_SOF_TRUNC } 108 109 frame.precision = p_bits 110 frame.height = height 111 frame.width = width 112 frame.n_components = nf 113 var max_h: i64 = 1 114 var max_v: i64 = 1 115 var i: i64 = 0 116 while i < nf { 117 let off: i64 = 6 + i * 3 118 let ci: i64 = payload[off] as i64 119 let hv: i64 = payload[off + 1] as i64 120 let hi: i64 = hv >> 4 121 let vi: i64 = hv & 0x0F 122 let tqi: i64 = payload[off + 2] as i64 123 if hi == 0 { return NX_JPEG_SOF_BAD_SAMPLING } 124 if hi > 4 { return NX_JPEG_SOF_BAD_SAMPLING } 125 if vi == 0 { return NX_JPEG_SOF_BAD_SAMPLING } 126 if vi > 4 { return NX_JPEG_SOF_BAD_SAMPLING } 127 if tqi > 3 { return NX_JPEG_SOF_BAD_TQ } 128 let comp: *NxJpegSofComponent = (frame.components as i64 + i * NX_JPEG_SOF_COMP_BYTES) as *NxJpegSofComponent 129 comp.ci = ci 130 comp.hi = hi 131 comp.vi = vi 132 comp.tqi = tqi 133 if hi > max_h { max_h = hi } 134 if vi > max_v { max_v = vi } 135 i = i + 1 136 } 137 frame.max_h = max_h 138 frame.max_v = max_v 139 return NX_JPEG_SOF_OK 140} 141 142// MCU dimensions in pixels, given the frame's max-h / max-v. 143// mcu_pixel_w = max_h * 8 144// mcu_pixel_h = max_v * 8 145// 146// For 4:4:4 (max_h = max_v = 1): MCU is 8x8. 147// For 4:2:2 (max_h = 2, max_v = 1): MCU is 16x8. 148// For 4:2:0 (max_h = max_v = 2): MCU is 16x16. 149func nx_jpeg_sof_mcu_pixel_width(frame: *NxJpegFrame) -> i64 { 150 return frame.max_h * 8 151} 152 153func nx_jpeg_sof_mcu_pixel_height(frame: *NxJpegFrame) -> i64 { 154 return frame.max_v * 8 155} 156 157// MCU count across image (ceil-divided). 158func nx_jpeg_sof_mcu_cols(frame: *NxJpegFrame) -> i64 { 159 let mw: i64 = nx_jpeg_sof_mcu_pixel_width(frame) 160 return (frame.width + mw - 1) / mw 161} 162 163func nx_jpeg_sof_mcu_rows(frame: *NxJpegFrame) -> i64 { 164 let mh: i64 = nx_jpeg_sof_mcu_pixel_height(frame) 165 return (frame.height + mh - 1) / mh 166}