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}