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/// Sign-coding context and XOR bit indexed by packed cardinal-neighbor state.
141#[rustfmt::skip]
142pub const SIGN_CONTEXT_LOOKUP: [(u8, u8); 256] = [
143    (9,0), (10,0), (10,1), (0,0), (12,0), (13,0), (11,0), (0,0), (12,1), (11,1),
144    (13,1), (0,0), (0,0), (0,0), (0,0), (0,0), (12,0), (13,0), (11,0), (0,0),
145    (12,0), (13,0), (11,0), (0,0), (9,0), (10,0), (10,1), (0,0), (0,0), (0,0),
146    (0,0), (0,0), (12,1), (11,1), (13,1), (0,0), (9,0), (10,0), (10,1), (0,0),
147    (12,1), (11,1), (13,1), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0),
148    (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0),
149    (0,0), (0,0), (0,0), (10,0), (10,0), (9,0), (0,0), (13,0), (13,0), (12,0),
150    (0,0), (11,1), (11,1), (12,1), (0,0), (0,0), (0,0), (0,0), (0,0), (13,0),
151    (13,0), (12,0), (0,0), (13,0), (13,0), (12,0), (0,0), (10,0), (10,0), (9,0),
152    (0,0), (0,0), (0,0), (0,0), (0,0), (11,1), (11,1), (12,1), (0,0), (10,0),
153    (10,0), (9,0), (0,0), (11,1), (11,1), (12,1), (0,0), (0,0), (0,0), (0,0),
154    (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0),
155    (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (10,1), (9,0), (10,1), (0,0),
156    (11,0), (12,0), (11,0), (0,0), (13,1), (12,1), (13,1), (0,0), (0,0), (0,0),
157    (0,0), (0,0), (11,0), (12,0), (11,0), (0,0), (11,0), (12,0), (11,0), (0,0),
158    (10,1), (9,0), (10,1), (0,0), (0,0), (0,0), (0,0), (0,0), (13,1), (12,1),
159    (13,1), (0,0), (10,1), (9,0), (10,1), (0,0), (13,1), (12,1), (13,1), (0,0),
160    (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0),
161    (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0),
162    (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0),
163    (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0),
164    (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0),
165    (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0),
166    (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0),
167    (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0),
168];
169
170const fn packed_sign_contexts() -> [u16; 256] {
171    let mut values = [0; 256];
172    let mut index = 0;
173    while index < values.len() {
174        let (context, xor) = SIGN_CONTEXT_LOOKUP[index];
175        values[index] = context as u16 | ((xor as u16) << 8);
176        index += 1;
177    }
178    values
179}
180
181/// Packed sign context in the low byte and XOR bit in the high byte.
182pub const PACKED_SIGN_CONTEXT_LOOKUP: [u16; 256] = packed_sign_contexts();
183
184/// Zero-coding contexts for LL and LH sub-bands.
185#[rustfmt::skip]
186pub const ZERO_CTX_LL_LH_LOOKUP: [u8; 256] = [
187    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,
188    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,
189    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,
190    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,
191    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,
192    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,
193    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,
194    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,
195    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,
196    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,
197    7, 7, 8, 8, 8, 8,
198];
199
200/// Zero-coding contexts for HL sub-bands.
201#[rustfmt::skip]
202pub const ZERO_CTX_HL_LOOKUP: [u8; 256] = [
203    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,
204    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,
205    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,
206    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,
207    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,
208    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,
209    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,
210    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,
211    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,
212    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,
213    7, 8, 7, 8, 7, 8,
214];
215
216/// Zero-coding contexts for HH sub-bands.
217#[rustfmt::skip]
218pub const ZERO_CTX_HH_LOOKUP: [u8; 256] = [
219    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,
220    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,
221    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,
222    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,
223    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,
224    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,
225    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,
226    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,
227    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,
228    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,
229    8, 8, 8, 8, 8, 8,
230];
231
232#[cfg(test)]
233mod tests {
234    use core::hint::black_box;
235
236    use super::*;
237
238    #[test]
239    fn irreversible_midpoint_bit_tracks_the_last_decoded_pass() {
240        assert_eq!(irreversible_midpoint_bit(4, 3, 1), Some(2));
241        assert_eq!(irreversible_midpoint_bit(2, 3, 2), Some(1));
242        assert_eq!(irreversible_midpoint_bit(4, 3, 2), Some(2));
243        assert_eq!(irreversible_midpoint_bit(4, 3, 3), Some(1));
244    }
245
246    #[test]
247    fn irreversible_midpoint_bit_rejects_empty_or_inconsistent_state() {
248        assert_eq!(irreversible_midpoint_bit(0, 3, 1), None);
249        assert_eq!(irreversible_midpoint_bit(4, 0, 1), None);
250        assert_eq!(irreversible_midpoint_bit(4, 3, 0), None);
251        assert_eq!(irreversible_midpoint_bit(1, 1, 5), None);
252    }
253
254    #[test]
255    fn device_tables_match_their_structured_sources_at_runtime() {
256        let build_qe: fn() -> [u32; 47] = mq_qe_values;
257        let build_transitions: fn() -> [u32; 47] = packed_mq_transitions;
258        let build_sign_contexts: fn() -> [u16; 256] = packed_sign_contexts;
259
260        let qe = black_box(build_qe)();
261        let transitions = black_box(build_transitions)();
262        let sign_contexts = black_box(build_sign_contexts)();
263
264        for (index, state) in MQ_STATES.iter().copied().enumerate() {
265            assert_eq!(qe[index], state.qe);
266            assert_eq!(
267                transitions[index],
268                u32::from(state.nmps)
269                    | (u32::from(state.nlps) << 8)
270                    | (u32::from(state.switch) << 16)
271            );
272        }
273        for (index, (context, xor)) in SIGN_CONTEXT_LOOKUP.iter().copied().enumerate() {
274            assert_eq!(
275                sign_contexts[index],
276                u16::from(context) | (u16::from(xor) << 8)
277            );
278        }
279    }
280}