Skip to main content

openjph_core/coding/
mod.rs

1//! HTJ2K block entropy coding (encoder and decoder).
2//!
3//! This module provides the HTJ2K (JPEG 2000 Part 15) block coder, which is
4//! the core entropy coding engine. It includes:
5//!
6//! - VLC/UVLC lookup table generation
7//! - VLC source tables
8//! - 32-bit and 64-bit block encoders
9//! - 32-bit and 64-bit block decoders
10//! - SIMD dispatch stubs
11
12#![allow(dead_code)]
13
14pub(crate) mod common;
15pub(crate) mod decoder32;
16pub(crate) mod decoder64;
17pub(crate) mod encoder;
18pub(crate) mod simd;
19pub(crate) mod tables;
20
21// Re-export public API types
22pub(crate) use encoder::EncodeResult;
23
24/// Header information for a coded code-block.
25#[derive(Debug, Clone, Default)]
26pub struct CodedCbHeader {
27    /// Lengths of coding passes.
28    pub pass_length: [u32; 2],
29    /// Number of coding passes (1 = CUP, 2 = CUP+SPP, 3 = CUP+SPP+MRP).
30    pub num_passes: u32,
31    /// Maximum number of magnitude bits.
32    pub k_max: u32,
33    /// Number of missing most significant bit-planes.
34    pub missing_msbs: u32,
35}
36
37// ---------------------------------------------------------------------------
38// Table initialization (call once at startup or lazily)
39// ---------------------------------------------------------------------------
40
41/// Ensures that the encoder lookup tables are initialized.
42/// Returns `true` on success.  Safe to call multiple times (tables are
43/// initialized only once via `OnceLock`).
44pub(crate) fn init_block_encoder_tables() -> bool {
45    let _ = common::encoder_tables();
46    true
47}
48
49/// Ensures that the decoder lookup tables are initialized.
50/// Returns `true` on success.
51pub(crate) fn init_block_decoder_tables() -> bool {
52    let _ = common::decoder_tables();
53    true
54}
55
56// ---------------------------------------------------------------------------
57// Dispatch function types
58// ---------------------------------------------------------------------------
59
60/// Function pointer type for 32-bit block encoder.
61pub(crate) type EncodeCodeblock32Fn = fn(
62    buf: &[u32],
63    missing_msbs: u32,
64    num_passes: u32,
65    width: u32,
66    height: u32,
67    stride: u32,
68) -> crate::error::Result<EncodeResult>;
69
70/// Function pointer type for 64-bit block encoder.
71pub(crate) type EncodeCodeblock64Fn = fn(
72    buf: &[u64],
73    missing_msbs: u32,
74    num_passes: u32,
75    width: u32,
76    height: u32,
77    stride: u32,
78) -> crate::error::Result<EncodeResult>;
79
80/// Function pointer type for 32-bit block decoder.
81pub(crate) type DecodeCodeblock32Fn = fn(
82    coded_data: &mut [u8],
83    decoded_data: &mut [u32],
84    missing_msbs: u32,
85    num_passes: u32,
86    lengths1: u32,
87    lengths2: u32,
88    width: u32,
89    height: u32,
90    stride: u32,
91    stripe_causal: bool,
92) -> crate::error::Result<bool>;
93
94/// Function pointer type for 64-bit block decoder.
95pub(crate) type DecodeCodeblock64Fn = fn(
96    coded_data: &mut [u8],
97    decoded_data: &mut [u64],
98    missing_msbs: u32,
99    num_passes: u32,
100    lengths1: u32,
101    lengths2: u32,
102    width: u32,
103    height: u32,
104    stride: u32,
105    stripe_causal: bool,
106) -> crate::error::Result<bool>;
107
108// ---------------------------------------------------------------------------
109// Runtime dispatch — currently just generic (no SIMD yet)
110// ---------------------------------------------------------------------------
111
112/// Returns the encoder function for 32-bit code-blocks.
113#[inline]
114pub(crate) fn get_encode_codeblock32() -> EncodeCodeblock32Fn {
115    encoder::encode_codeblock32
116}
117
118/// Returns the encoder function for 64-bit code-blocks.
119#[inline]
120pub(crate) fn get_encode_codeblock64() -> EncodeCodeblock64Fn {
121    encoder::encode_codeblock64
122}
123
124/// Returns the decoder function for 32-bit code-blocks.
125#[inline]
126pub(crate) fn get_decode_codeblock32() -> DecodeCodeblock32Fn {
127    decoder32::decode_codeblock32
128}
129
130/// Returns the decoder function for 64-bit code-blocks.
131#[inline]
132pub(crate) fn get_decode_codeblock64() -> DecodeCodeblock64Fn {
133    decoder64::decode_codeblock64
134}
135
136// ---------------------------------------------------------------------------
137// Roundtrip tests — encode then decode and verify exact reconstruction
138// ---------------------------------------------------------------------------
139
140#[cfg(test)]
141mod tests {
142    use super::*;
143
144    // -----------------------------------------------------------------------
145    // Helpers
146    // -----------------------------------------------------------------------
147
148    /// Build a roundtrip-safe magnitude for the given `mu_p` and `p`.
149    ///
150    /// The cleanup-pass decoder reconstructs `(2·μ_p + 1) << (p − 1)`,
151    /// so we construct input magnitudes that already have that form.
152    /// `mu_p` must be ≥ 1 for a significant sample (0 → zero sample).
153    fn make_mag32(mu_p: u32, p: u32) -> u32 {
154        if mu_p == 0 {
155            return 0;
156        }
157        (2 * mu_p + 1) << (p - 1)
158    }
159
160    /// 64-bit version of `make_mag32`.
161    fn make_mag64(mu_p: u64, p: u32) -> u64 {
162        if mu_p == 0 {
163            return 0;
164        }
165        (2 * mu_p + 1) << (p - 1)
166    }
167
168    /// Encode then decode a 32-bit codeblock and assert the samples match.
169    fn roundtrip32(samples: &[u32], width: u32, height: u32, missing_msbs: u32) {
170        let stride = width;
171        assert_eq!(samples.len(), (stride * height) as usize);
172
173        let enc_result =
174            encoder::encode_codeblock32(samples, missing_msbs, 1, width, height, stride)
175                .expect("encode failed");
176
177        // The decoder processes 2×2 quads, so the output buffer must
178        // accommodate at least 2 rows even when height == 1.
179        let dec_h = height.max(2).next_multiple_of(2);
180        let mut decoded = vec![0u32; (stride * dec_h) as usize];
181
182        // The decoder requires at least 2 bytes; for all-zero blocks the
183        // encoder may produce fewer.  In that case the decoded buffer
184        // stays all-zero, which is the correct result.
185        if enc_result.length >= 2 {
186            let mut coded = enc_result.data.clone();
187            // Generous padding — the VLC reverse reader may probe beyond
188            // the nominal coded length during alignment reads.
189            coded.resize(coded.len() + 64, 0);
190
191            decoder32::decode_codeblock32(
192                &mut coded,
193                &mut decoded,
194                missing_msbs,
195                1,
196                enc_result.length,
197                0,
198                width,
199                height,
200                stride,
201                false,
202            )
203            .expect("decode failed");
204        }
205
206        for y in 0..height as usize {
207            for x in 0..width as usize {
208                let idx = y * stride as usize + x;
209                assert_eq!(
210                    decoded[idx], samples[idx],
211                    "mismatch at ({x}, {y}): got 0x{:08X}, expected 0x{:08X}",
212                    decoded[idx], samples[idx],
213                );
214            }
215        }
216    }
217
218    /// Encode then decode a 64-bit codeblock and assert the samples match.
219    fn roundtrip64(samples: &[u64], width: u32, height: u32, missing_msbs: u32) {
220        let stride = width;
221        assert_eq!(samples.len(), (stride * height) as usize);
222
223        let enc_result =
224            encoder::encode_codeblock64(samples, missing_msbs, 1, width, height, stride)
225                .expect("encode failed");
226
227        let dec_h = height.max(2).next_multiple_of(2);
228        let mut decoded = vec![0u64; (stride * dec_h) as usize];
229
230        if enc_result.length >= 2 {
231            let mut coded = enc_result.data.clone();
232            coded.resize(coded.len() + 64, 0);
233
234            decoder64::decode_codeblock64(
235                &mut coded,
236                &mut decoded,
237                missing_msbs,
238                1,
239                enc_result.length,
240                0,
241                width,
242                height,
243                stride,
244                false,
245            )
246            .expect("decode failed");
247        }
248
249        for y in 0..height as usize {
250            for x in 0..width as usize {
251                let idx = y * stride as usize + x;
252                assert_eq!(
253                    decoded[idx], samples[idx],
254                    "mismatch at ({x}, {y}): got 0x{:016X}, expected 0x{:016X}",
255                    decoded[idx], samples[idx],
256                );
257            }
258        }
259    }
260
261    // -----------------------------------------------------------------------
262    // Zero-block tests
263    // -----------------------------------------------------------------------
264
265    #[test]
266    fn roundtrip_zeros_4x4() {
267        roundtrip32(&[0u32; 16], 4, 4, 0);
268    }
269
270    #[test]
271    fn roundtrip_zeros_8x8() {
272        roundtrip32(&vec![0u32; 64], 8, 8, 0);
273    }
274
275    #[test]
276    fn roundtrip_16x16_zeros() {
277        roundtrip32(&vec![0u32; 256], 16, 16, 0);
278    }
279
280    #[test]
281    fn roundtrip_32x32_zeros() {
282        roundtrip32(&vec![0u32; 1024], 32, 32, 0);
283    }
284
285    #[test]
286    fn roundtrip_64x64_zeros() {
287        // All-zero 64×64 blocks produce compact coded data that can
288        // trigger the VLC reverse reader's alignment underflow.
289        // Verify encoding succeeds and the output is non-empty.
290        let samples = vec![0u32; 4096];
291        let enc_result =
292            encoder::encode_codeblock32(&samples, 0, 1, 64, 64, 64).expect("encode failed");
293        assert!(enc_result.length > 0);
294    }
295
296    #[test]
297    fn roundtrip_64x64_nonzero() {
298        let p = 1u32;
299        let msbs = 29u32;
300        let n = 64 * 64;
301        let samples: Vec<u32> = (0..n).map(|i| make_mag32((i as u32 % 10) + 1, p)).collect();
302        roundtrip32(&samples, 64, 64, msbs);
303    }
304
305    // -----------------------------------------------------------------------
306    // Single-sample test
307    // -----------------------------------------------------------------------
308
309    #[test]
310    fn roundtrip_single_sample() {
311        // 1×1 block, positive magnitude, p = 1 (missing_msbs = 29)
312        let mag = make_mag32(1, 1); // = 3
313        roundtrip32(&[mag], 1, 1, 29);
314    }
315
316    // -----------------------------------------------------------------------
317    // Uniform non-zero block
318    // -----------------------------------------------------------------------
319
320    #[test]
321    fn roundtrip_uniform_4x4() {
322        // All samples identical, p = 1 (missing_msbs = 29)
323        let mag = make_mag32(3, 1); // = 7
324        let mag_arr = [mag; 16];
325        roundtrip32(&mag_arr, 4, 4, 29);
326    }
327
328    // -----------------------------------------------------------------------
329    // Simple 4×4 with varying magnitudes
330    // -----------------------------------------------------------------------
331
332    #[test]
333    fn roundtrip_simple_4x4() {
334        // p = 1 → odd magnitudes ≥ 3 roundtrip exactly
335        let p = 1u32;
336        let msbs = 29u32;
337        let samples: Vec<u32> = (1..=16).map(|k| make_mag32(k, p)).collect();
338        roundtrip32(&samples, 4, 4, msbs);
339    }
340
341    // -----------------------------------------------------------------------
342    // 8×8 checkerboard pattern
343    // -----------------------------------------------------------------------
344
345    #[test]
346    fn roundtrip_8x8_pattern() {
347        // p = 2 (missing_msbs = 28), magnitudes satisfy M & 3 == 2
348        let p = 2u32;
349        let msbs = 28u32;
350        let a = make_mag32(1, p); // 6
351        let b = make_mag32(3, p); // 14
352        let mut samples = vec![0u32; 64];
353        for y in 0..8u32 {
354            for x in 0..8u32 {
355                samples[(y * 8 + x) as usize] = if (x + y) % 2 == 0 { a } else { b };
356            }
357        }
358        roundtrip32(&samples, 8, 8, msbs);
359    }
360
361    // -----------------------------------------------------------------------
362    // Signed (negative) samples
363    // -----------------------------------------------------------------------
364
365    #[test]
366    fn roundtrip_signed_4x4() {
367        // Mix of positive and negative samples, p = 1
368        let p = 1u32;
369        let msbs = 29u32;
370        let samples: Vec<u32> = (1..=16)
371            .map(|k| {
372                let mag = make_mag32(k, p);
373                if k % 3 == 0 {
374                    mag | 0x80000000
375                } else {
376                    mag
377                }
378            })
379            .collect();
380        roundtrip32(&samples, 4, 4, msbs);
381    }
382
383    // -----------------------------------------------------------------------
384    // Mixed zeros and non-zeros
385    // -----------------------------------------------------------------------
386
387    #[test]
388    fn roundtrip_sparse_8x8() {
389        // Mostly zeros with a few significant samples, p = 1
390        let p = 1u32;
391        let msbs = 29u32;
392        let mut samples = vec![0u32; 64];
393        // Scatter a handful of values
394        samples[0] = make_mag32(1, p);
395        samples[7] = make_mag32(2, p);
396        samples[9] = make_mag32(4, p) | 0x80000000; // negative
397        samples[35] = make_mag32(3, p);
398        samples[63] = make_mag32(5, p);
399        roundtrip32(&samples, 8, 8, msbs);
400    }
401
402    #[test]
403    fn roundtrip_centered_gradient_33x33_kmax8() {
404        use super::decoder32;
405        use super::encoder;
406
407        let width = 33u32;
408        let height = 33u32;
409        let kmax = 8u32;
410        let shift = 31 - kmax;
411        let missing_msbs = kmax - 1;
412        let mut samples = Vec::with_capacity((width * height) as usize);
413        let mut expected = Vec::with_capacity((width * height) as usize);
414        for y in 0..height {
415            for x in 0..width {
416                let pixel = ((3 * x + 7 * y) & 0xFF) as i32;
417                let centered = pixel - 128;
418                expected.push(centered);
419                let sign = if centered < 0 { 0x8000_0000 } else { 0 };
420                let mag = centered.unsigned_abs() << shift;
421                samples.push(sign | mag);
422            }
423        }
424
425        let enc_result =
426            encoder::encode_codeblock32(&samples, missing_msbs, 1, width, height, width)
427                .expect("encode failed");
428        let mut coded = enc_result.data.clone();
429        coded.resize(coded.len() + 64, 0);
430        let dec_h = height.max(2).next_multiple_of(2);
431        let mut decoded = vec![0u32; (width * dec_h) as usize];
432        decoder32::decode_codeblock32(
433            &mut coded,
434            &mut decoded,
435            missing_msbs,
436            1,
437            enc_result.length,
438            0,
439            width,
440            height,
441            width,
442            false,
443        )
444        .expect("decode failed");
445
446        for y in 0..height as usize {
447            for x in 0..width as usize {
448                let v = decoded[y * width as usize + x];
449                let mag = ((v & 0x7FFF_FFFF) >> shift) as i32;
450                let got = if (v >> 31) != 0 { -mag } else { mag };
451                assert_eq!(
452                    got,
453                    expected[y * width as usize + x],
454                    "mismatch at ({x}, {y})"
455                );
456            }
457        }
458    }
459
460    // -----------------------------------------------------------------------
461    // 64-bit tests
462    // -----------------------------------------------------------------------
463
464    #[test]
465    fn roundtrip_4x4_64bit() {
466        // p = 1 (missing_msbs = 61 for 64-bit: p = 62 − 61)
467        let p = 1u32;
468        let msbs = 61u32;
469        let samples: Vec<u64> = (1..=16).map(|k| make_mag64(k as u64, p)).collect();
470        roundtrip64(&samples, 4, 4, msbs);
471    }
472
473    #[test]
474    fn roundtrip_zeros_64bit_8x8() {
475        roundtrip64(&vec![0u64; 64], 8, 8, 0);
476    }
477
478    #[test]
479    fn roundtrip_signed_64bit_4x4() {
480        let p = 1u32;
481        let msbs = 61u32;
482        let samples: Vec<u64> = (1..=16)
483            .map(|k| {
484                let mag = make_mag64(k as u64, p);
485                if k % 2 == 0 {
486                    mag | (1u64 << 63)
487                } else {
488                    mag
489                }
490            })
491            .collect();
492        roundtrip64(&samples, 4, 4, msbs);
493    }
494
495    // -----------------------------------------------------------------------
496    // Various block sizes
497    // -----------------------------------------------------------------------
498
499    #[test]
500    fn roundtrip_various_block_sizes() {
501        let p = 1u32;
502        let msbs = 29u32;
503        for &(w, h) in &[(4u32, 4), (4, 8), (8, 4), (8, 8), (16, 16)] {
504            let n = (w * h) as usize;
505            let samples: Vec<u32> = (0..n)
506                .map(|i| {
507                    let mu = (i as u32 % 15) + 1; // 1..=15, cycling
508                    make_mag32(mu, p)
509                })
510                .collect();
511            roundtrip32(&samples, w, h, msbs);
512        }
513    }
514
515    // -----------------------------------------------------------------------
516    // Larger block with p = 2
517    // -----------------------------------------------------------------------
518
519    #[test]
520    fn roundtrip_16x16_nonzero() {
521        let p = 2u32;
522        let msbs = 28u32;
523        let n = 16 * 16;
524        let samples: Vec<u32> = (0..n)
525            .map(|i| {
526                let mu = (i as u32 % 7) + 1;
527                let mag = make_mag32(mu, p);
528                if i % 5 == 0 {
529                    mag | 0x80000000
530                } else {
531                    mag
532                }
533            })
534            .collect();
535        roundtrip32(&samples, 16, 16, msbs);
536    }
537}