Skip to main content

j2k_codec_math/
classic.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3//! Shared JPEG 2000 classic Tier-1 probability and context tables.
4
5/// One JPEG 2000 MQ probability-state transition.
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub struct ClassicMqState {
8    /// Probability estimate.
9    pub qe: u32,
10    /// Next state after an MPS event.
11    pub nmps: u8,
12    /// Next state after an LPS event.
13    pub nlps: u8,
14    /// Whether an LPS event switches the MPS bit.
15    pub switch: bool,
16}
17
18macro_rules! mq_states {
19    ($($qe:expr, $nmps:expr, $nlps:expr, $switch:expr),+ $(,)?) => {
20        [$(ClassicMqState { qe: $qe, nmps: $nmps, nlps: $nlps, switch: $switch }),+]
21    };
22}
23
24/// MQ values and state transitions from ITU-T T.800 Table C.2.
25#[rustfmt::skip]
26pub const MQ_STATES: [ClassicMqState; 47] = mq_states!(
27    0x5601, 1, 1, true,
28    0x3401, 2, 6, false,
29    0x1801, 3, 9, false,
30    0x0AC1, 4, 12, false,
31    0x0521, 5, 29, false,
32    0x0221, 38, 33, false,
33    0x5601, 7, 6, true,
34    0x5401, 8, 14, false,
35    0x4801, 9, 14, false,
36    0x3801, 10, 14, false,
37    0x3001, 11, 17, false,
38    0x2401, 12, 18, false,
39    0x1C01, 13, 20, false,
40    0x1601, 29, 21, false,
41    0x5601, 15, 14, true,
42    0x5401, 16, 14, false,
43    0x5101, 17, 15, false,
44    0x4801, 18, 16, false,
45    0x3801, 19, 17, false,
46    0x3401, 20, 18, false,
47    0x3001, 21, 19, false,
48    0x2801, 22, 19, false,
49    0x2401, 23, 20, false,
50    0x2201, 24, 21, false,
51    0x1C01, 25, 22, false,
52    0x1801, 26, 23, false,
53    0x1601, 27, 24, false,
54    0x1401, 28, 25, false,
55    0x1201, 29, 26, false,
56    0x1101, 30, 27, false,
57    0x0AC1, 31, 28, false,
58    0x09C1, 32, 29, false,
59    0x08A1, 33, 30, false,
60    0x0521, 34, 31, false,
61    0x0441, 35, 32, false,
62    0x02A1, 36, 33, false,
63    0x0221, 37, 34, false,
64    0x0141, 38, 35, false,
65    0x0111, 39, 36, false,
66    0x0085, 40, 37, false,
67    0x0049, 41, 38, false,
68    0x0025, 42, 39, false,
69    0x0015, 43, 40, false,
70    0x0009, 44, 41, false,
71    0x0005, 45, 42, false,
72    0x0001, 45, 43, false,
73    0x5601, 46, 46, false,
74);
75
76const fn mq_qe_values() -> [u32; 47] {
77    let mut values = [0; 47];
78    let mut index = 0;
79    while index < values.len() {
80        values[index] = MQ_STATES[index].qe;
81        index += 1;
82    }
83    values
84}
85
86const fn packed_mq_transitions() -> [u32; 47] {
87    let mut values = [0; 47];
88    let mut index = 0;
89    while index < values.len() {
90        let state = MQ_STATES[index];
91        values[index] =
92            state.nmps as u32 | ((state.nlps as u32) << 8) | ((state.switch as u32) << 16);
93        index += 1;
94    }
95    values
96}
97
98/// Primitive MQ probability values for device compilers that cannot lower arrays of structs.
99pub const MQ_QE_VALUES: [u32; 47] = mq_qe_values();
100/// Packed MQ MPS/LPS transitions and switch flag for device lookup tables.
101pub const PACKED_MQ_TRANSITION_VALUES: [u32; 47] = packed_mq_transitions();
102
103/// Returns the bit position of the half-bin term used to reconstruct an
104/// irreversible coefficient after the final decoded coding pass.
105///
106/// `None` means there is no non-zero coefficient to reconstruct or the pass
107/// count is inconsistent with the number of decoded bitplanes.
108pub const fn irreversible_midpoint_bit(
109    magnitude: u64,
110    decoded_bitplanes: u32,
111    number_of_coding_passes: u32,
112) -> Option<u32> {
113    if magnitude == 0 || decoded_bitplanes == 0 || number_of_coding_passes == 0 {
114        return None;
115    }
116
117    let final_pass = number_of_coding_passes - 1;
118    let decoded_plane = final_pass.div_ceil(3);
119    let Some(mut lowest_decoded_bit) = decoded_bitplanes.checked_sub(decoded_plane + 1) else {
120        return None;
121    };
122    if lowest_decoded_bit >= u64::BITS {
123        return None;
124    }
125
126    // A final significance-propagation pass does not refine coefficients that
127    // were already significant. A newly significant coefficient has the
128    // current bit set, distinguishing the two cases without decoder state.
129    if final_pass % 3 == 1 && magnitude & (1_u64 << lowest_decoded_bit) == 0 {
130        lowest_decoded_bit += 1;
131    }
132
133    if lowest_decoded_bit < u64::BITS {
134        Some(lowest_decoded_bit)
135    } else {
136        None
137    }
138}
139
140/// Returns the bit to set in a reversible (5/3) coefficient's magnitude when
141/// its code block's coding passes stopped before its lowest bit-planes.
142///
143/// T.800 E.1.2.2 reconstructs a fully decoded reversible coefficient exactly
144/// (Equation E-7) and otherwise as `q ± r·2^(Mb−Nb)` (Equation E-8), with the
145/// reconstruction parameter `r` chosen by the decoder (E.1.1.2). With
146/// `r = 1/2` and `b` undecoded low bit-planes the offset is `2^(b−1)`, the
147/// same placement as [`irreversible_midpoint_bit`], and it stays an integer.
148/// The undecoded bits are zero, so setting the returned bit adds the offset.
149///
150/// `None` means the coefficient is zero, fully decoded, or the pass count is
151/// inconsistent with the number of decoded bitplanes; it is then unchanged.
152// `#[inline]` exports the MIR the cuda-oxide device compiler needs; rustc
153// exports it unprompted only for leaf functions like `irreversible_midpoint_bit`.
154#[inline]
155pub const fn reversible_midpoint_bit(
156    magnitude: u64,
157    decoded_bitplanes: u32,
158    number_of_coding_passes: u32,
159) -> Option<u32> {
160    match irreversible_midpoint_bit(magnitude, decoded_bitplanes, number_of_coding_passes) {
161        Some(lowest_decoded_bit) if lowest_decoded_bit > 0 => Some(lowest_decoded_bit - 1),
162        _ => None,
163    }
164}
165
166/// Sign-coding context and XOR bit indexed by packed cardinal-neighbor state.
167#[rustfmt::skip]
168pub const SIGN_CONTEXT_LOOKUP: [(u8, u8); 256] = [
169    (9,0), (10,0), (10,1), (0,0), (12,0), (13,0), (11,0), (0,0), (12,1), (11,1),
170    (13,1), (0,0), (0,0), (0,0), (0,0), (0,0), (12,0), (13,0), (11,0), (0,0),
171    (12,0), (13,0), (11,0), (0,0), (9,0), (10,0), (10,1), (0,0), (0,0), (0,0),
172    (0,0), (0,0), (12,1), (11,1), (13,1), (0,0), (9,0), (10,0), (10,1), (0,0),
173    (12,1), (11,1), (13,1), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0),
174    (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0),
175    (0,0), (0,0), (0,0), (10,0), (10,0), (9,0), (0,0), (13,0), (13,0), (12,0),
176    (0,0), (11,1), (11,1), (12,1), (0,0), (0,0), (0,0), (0,0), (0,0), (13,0),
177    (13,0), (12,0), (0,0), (13,0), (13,0), (12,0), (0,0), (10,0), (10,0), (9,0),
178    (0,0), (0,0), (0,0), (0,0), (0,0), (11,1), (11,1), (12,1), (0,0), (10,0),
179    (10,0), (9,0), (0,0), (11,1), (11,1), (12,1), (0,0), (0,0), (0,0), (0,0),
180    (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0),
181    (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (10,1), (9,0), (10,1), (0,0),
182    (11,0), (12,0), (11,0), (0,0), (13,1), (12,1), (13,1), (0,0), (0,0), (0,0),
183    (0,0), (0,0), (11,0), (12,0), (11,0), (0,0), (11,0), (12,0), (11,0), (0,0),
184    (10,1), (9,0), (10,1), (0,0), (0,0), (0,0), (0,0), (0,0), (13,1), (12,1),
185    (13,1), (0,0), (10,1), (9,0), (10,1), (0,0), (13,1), (12,1), (13,1), (0,0),
186    (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0),
187    (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0),
188    (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0),
189    (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0),
190    (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0),
191    (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0),
192    (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0),
193    (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0),
194];
195
196const fn packed_sign_contexts() -> [u16; 256] {
197    let mut values = [0; 256];
198    let mut index = 0;
199    while index < values.len() {
200        let (context, xor) = SIGN_CONTEXT_LOOKUP[index];
201        values[index] = context as u16 | ((xor as u16) << 8);
202        index += 1;
203    }
204    values
205}
206
207/// Packed sign context in the low byte and XOR bit in the high byte.
208pub const PACKED_SIGN_CONTEXT_LOOKUP: [u16; 256] = packed_sign_contexts();
209
210/// Zero-coding contexts for LL and LH sub-bands.
211#[rustfmt::skip]
212pub const ZERO_CTX_LL_LH_LOOKUP: [u8; 256] = [
213    0, 3, 1, 3, 5, 7, 6, 7, 1, 3, 2, 3, 6, 7, 6, 7, 5, 7, 6, 7, 8, 8, 8, 8, 6,
214    7, 6, 7, 8, 8, 8, 8, 1, 3, 2, 3, 6, 7, 6, 7, 2, 3, 2, 3, 6, 7, 6, 7, 6, 7,
215    6, 7, 8, 8, 8, 8, 6, 7, 6, 7, 8, 8, 8, 8, 3, 4, 3, 4, 7, 7, 7, 7, 3, 4, 3,
216    4, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 7, 7, 7, 7, 8, 8, 8, 8, 3, 4, 3, 4,
217    7, 7, 7, 7, 3, 4, 3, 4, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 7, 7, 7, 7, 8,
218    8, 8, 8, 1, 3, 2, 3, 6, 7, 6, 7, 2, 3, 2, 3, 6, 7, 6, 7, 6, 7, 6, 7, 8, 8,
219    8, 8, 6, 7, 6, 7, 8, 8, 8, 8, 2, 3, 2, 3, 6, 7, 6, 7, 2, 3, 2, 3, 6, 7, 6,
220    7, 6, 7, 6, 7, 8, 8, 8, 8, 6, 7, 6, 7, 8, 8, 8, 8, 3, 4, 3, 4, 7, 7, 7, 7,
221    3, 4, 3, 4, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 7, 7, 7, 7, 8, 8, 8, 8, 3,
222    4, 3, 4, 7, 7, 7, 7, 3, 4, 3, 4, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 7, 7,
223    7, 7, 8, 8, 8, 8,
224];
225
226/// Zero-coding contexts for HL sub-bands.
227#[rustfmt::skip]
228pub const ZERO_CTX_HL_LOOKUP: [u8; 256] = [
229    0, 5, 1, 6, 3, 7, 3, 7, 1, 6, 2, 6, 3, 7, 3, 7, 3, 7, 3, 7, 4, 7, 4, 7, 3,
230    7, 3, 7, 4, 7, 4, 7, 1, 6, 2, 6, 3, 7, 3, 7, 2, 6, 2, 6, 3, 7, 3, 7, 3, 7,
231    3, 7, 4, 7, 4, 7, 3, 7, 3, 7, 4, 7, 4, 7, 5, 8, 6, 8, 7, 8, 7, 8, 6, 8, 6,
232    8, 7, 8, 7, 8, 7, 8, 7, 8, 7, 8, 7, 8, 7, 8, 7, 8, 7, 8, 7, 8, 6, 8, 6, 8,
233    7, 8, 7, 8, 6, 8, 6, 8, 7, 8, 7, 8, 7, 8, 7, 8, 7, 8, 7, 8, 7, 8, 7, 8, 7,
234    8, 7, 8, 1, 6, 2, 6, 3, 7, 3, 7, 2, 6, 2, 6, 3, 7, 3, 7, 3, 7, 3, 7, 4, 7,
235    4, 7, 3, 7, 3, 7, 4, 7, 4, 7, 2, 6, 2, 6, 3, 7, 3, 7, 2, 6, 2, 6, 3, 7, 3,
236    7, 3, 7, 3, 7, 4, 7, 4, 7, 3, 7, 3, 7, 4, 7, 4, 7, 6, 8, 6, 8, 7, 8, 7, 8,
237    6, 8, 6, 8, 7, 8, 7, 8, 7, 8, 7, 8, 7, 8, 7, 8, 7, 8, 7, 8, 7, 8, 7, 8, 6,
238    8, 6, 8, 7, 8, 7, 8, 6, 8, 6, 8, 7, 8, 7, 8, 7, 8, 7, 8, 7, 8, 7, 8, 7, 8,
239    7, 8, 7, 8, 7, 8,
240];
241
242/// Zero-coding contexts for HH sub-bands.
243#[rustfmt::skip]
244pub const ZERO_CTX_HH_LOOKUP: [u8; 256] = [
245    0, 1, 3, 4, 1, 2, 4, 5, 3, 4, 6, 7, 4, 5, 7, 7, 1, 2, 4, 5, 2, 2, 5, 5, 4,
246    5, 7, 7, 5, 5, 7, 7, 3, 4, 6, 7, 4, 5, 7, 7, 6, 7, 8, 8, 7, 7, 8, 8, 4, 5,
247    7, 7, 5, 5, 7, 7, 7, 7, 8, 8, 7, 7, 8, 8, 1, 2, 4, 5, 2, 2, 5, 5, 4, 5, 7,
248    7, 5, 5, 7, 7, 2, 2, 5, 5, 2, 2, 5, 5, 5, 5, 7, 7, 5, 5, 7, 7, 4, 5, 7, 7,
249    5, 5, 7, 7, 7, 7, 8, 8, 7, 7, 8, 8, 5, 5, 7, 7, 5, 5, 7, 7, 7, 7, 8, 8, 7,
250    7, 8, 8, 3, 4, 6, 7, 4, 5, 7, 7, 6, 7, 8, 8, 7, 7, 8, 8, 4, 5, 7, 7, 5, 5,
251    7, 7, 7, 7, 8, 8, 7, 7, 8, 8, 6, 7, 8, 8, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8,
252    8, 7, 7, 8, 8, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 4, 5, 7, 7, 5, 5, 7, 7,
253    7, 7, 8, 8, 7, 7, 8, 8, 5, 5, 7, 7, 5, 5, 7, 7, 7, 7, 8, 8, 7, 7, 8, 8, 7,
254    7, 8, 8, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 7, 7, 8, 8, 7, 7, 8, 8, 8, 8,
255    8, 8, 8, 8, 8, 8,
256];
257
258#[cfg(test)]
259mod tests {
260    use core::hint::black_box;
261
262    use super::*;
263
264    #[test]
265    fn irreversible_midpoint_bit_tracks_the_last_decoded_pass() {
266        assert_eq!(irreversible_midpoint_bit(4, 3, 1), Some(2));
267        assert_eq!(irreversible_midpoint_bit(2, 3, 2), Some(1));
268        assert_eq!(irreversible_midpoint_bit(4, 3, 2), Some(2));
269        assert_eq!(irreversible_midpoint_bit(4, 3, 3), Some(1));
270    }
271
272    #[test]
273    fn irreversible_midpoint_bit_rejects_empty_or_inconsistent_state() {
274        assert_eq!(irreversible_midpoint_bit(0, 3, 1), None);
275        assert_eq!(irreversible_midpoint_bit(4, 0, 1), None);
276        assert_eq!(irreversible_midpoint_bit(4, 3, 0), None);
277        assert_eq!(irreversible_midpoint_bit(1, 1, 5), None);
278    }
279
280    #[test]
281    fn reversible_midpoint_bit_sits_below_the_lowest_decoded_bit() {
282        // Three bitplanes allow 1 + 3 * 2 = 7 passes; a complete block is exact.
283        assert_eq!(reversible_midpoint_bit(5, 3, 7), None);
284        // A final magnitude-refinement pass refines every significant coefficient.
285        assert_eq!(reversible_midpoint_bit(4, 3, 6), None);
286        // A final significance pass leaves 0b100 unrefined at bit 0 but not 0b001.
287        assert_eq!(reversible_midpoint_bit(4, 3, 5), Some(0));
288        assert_eq!(reversible_midpoint_bit(1, 3, 5), None);
289        // After bit-plane 1's cleanup pass, bit 0 is undecoded.
290        assert_eq!(reversible_midpoint_bit(4, 3, 4), Some(0));
291        // After the first cleanup pass, bits 1 and 0 are undecoded.
292        assert_eq!(reversible_midpoint_bit(4, 3, 1), Some(1));
293        assert_eq!(reversible_midpoint_bit(0, 3, 1), None);
294        assert_eq!(reversible_midpoint_bit(1, 1, 5), None);
295    }
296
297    #[test]
298    fn device_tables_match_their_structured_sources_at_runtime() {
299        let build_qe: fn() -> [u32; 47] = mq_qe_values;
300        let build_transitions: fn() -> [u32; 47] = packed_mq_transitions;
301        let build_sign_contexts: fn() -> [u16; 256] = packed_sign_contexts;
302
303        let qe = black_box(build_qe)();
304        let transitions = black_box(build_transitions)();
305        let sign_contexts = black_box(build_sign_contexts)();
306
307        for (index, state) in MQ_STATES.iter().copied().enumerate() {
308            assert_eq!(qe[index], state.qe);
309            assert_eq!(
310                transitions[index],
311                u32::from(state.nmps)
312                    | (u32::from(state.nlps) << 8)
313                    | (u32::from(state.switch) << 16)
314            );
315        }
316        for (index, (context, xor)) in SIGN_CONTEXT_LOOKUP.iter().copied().enumerate() {
317            assert_eq!(
318                sign_contexts[index],
319                u16::from(context) | (u16::from(xor) << 8)
320            );
321        }
322    }
323}