Skip to main content

dicom_toolkit_codec/jpeg_ls/
sample.rs

1//! Sample trait for abstracting bit-depth differences in JPEG-LS.
2//!
3//! Provides type-level dispatch for 8-bit (`u8`) and 16-bit (`u16`) pixel values,
4//! replacing the C++ template specializations in CharLS (`LosslessTraitsT`, `DefaultTraitsT`).
5
6/// Trait abstracting a pixel sample type for JPEG-LS encoding/decoding.
7pub trait Sample:
8    Copy + Clone + Default + Into<i32> + std::fmt::Debug + Send + Sync + 'static
9{
10    /// The maximum sample value (e.g. 255 for 8-bit, 65535 for 16-bit).
11    const MAX_VAL_DEFAULT: i32;
12
13    /// Number of bytes per sample.
14    const BYTES: usize;
15
16    /// Convert from an i32, clamping to [0, max_val].
17    fn from_i32_clamped(val: i32, max_val: i32) -> Self;
18
19    /// Read a sample from a byte slice in native (little-endian) order.
20    fn read_le(data: &[u8], offset: usize) -> Self;
21
22    /// Write a sample to a byte slice in native (little-endian) order.
23    fn write_le(data: &mut [u8], offset: usize, val: Self);
24}
25
26impl Sample for u8 {
27    const MAX_VAL_DEFAULT: i32 = 255;
28    const BYTES: usize = 1;
29
30    #[inline]
31    fn from_i32_clamped(val: i32, max_val: i32) -> Self {
32        val.max(0).min(max_val) as u8
33    }
34
35    #[inline]
36    fn read_le(data: &[u8], offset: usize) -> Self {
37        data[offset]
38    }
39
40    #[inline]
41    fn write_le(data: &mut [u8], offset: usize, val: Self) {
42        data[offset] = val;
43    }
44}
45
46impl Sample for u16 {
47    const MAX_VAL_DEFAULT: i32 = 65535;
48    const BYTES: usize = 2;
49
50    #[inline]
51    fn from_i32_clamped(val: i32, max_val: i32) -> Self {
52        val.max(0).min(max_val) as u16
53    }
54
55    #[inline]
56    fn read_le(data: &[u8], offset: usize) -> Self {
57        let lo = data[offset] as u16;
58        let hi = data[offset + 1] as u16;
59        lo | (hi << 8)
60    }
61
62    #[inline]
63    fn write_le(data: &mut [u8], offset: usize, val: Self) {
64        data[offset] = val as u8;
65        data[offset + 1] = (val >> 8) as u8;
66    }
67}
68
69/// Dispatch helper: returns true if the given bits_per_sample fits in u8.
70pub fn needs_u16(bits_per_sample: u8) -> bool {
71    bits_per_sample > 8
72}
73
74// ── Tests ─────────────────────────────────────────────────────────────────────
75
76#[cfg(test)]
77mod tests {
78    use super::*;
79
80    #[test]
81    fn u8_sample_basics() {
82        assert_eq!(u8::MAX_VAL_DEFAULT, 255);
83        assert_eq!(u8::BYTES, 1);
84        assert_eq!(u8::from_i32_clamped(100, 255), 100u8);
85        assert_eq!(u8::from_i32_clamped(300, 255), 255u8);
86        assert_eq!(u8::from_i32_clamped(-5, 255), 0u8);
87    }
88
89    #[test]
90    fn u16_sample_basics() {
91        assert_eq!(u16::MAX_VAL_DEFAULT, 65535);
92        assert_eq!(u16::BYTES, 2);
93        assert_eq!(u16::from_i32_clamped(1000, 4095), 1000u16);
94        assert_eq!(u16::from_i32_clamped(5000, 4095), 4095u16);
95        assert_eq!(u16::from_i32_clamped(-1, 4095), 0u16);
96    }
97
98    #[test]
99    fn u8_read_write_le() {
100        let mut buf = [0u8; 4];
101        u8::write_le(&mut buf, 0, 42);
102        u8::write_le(&mut buf, 1, 255);
103        assert_eq!(u8::read_le(&buf, 0), 42);
104        assert_eq!(u8::read_le(&buf, 1), 255);
105    }
106
107    #[test]
108    fn u16_read_write_le() {
109        let mut buf = [0u8; 8];
110        u16::write_le(&mut buf, 0, 0x1234);
111        u16::write_le(&mut buf, 2, 0xABCD);
112        assert_eq!(u16::read_le(&buf, 0), 0x1234);
113        assert_eq!(u16::read_le(&buf, 2), 0xABCD);
114    }
115
116    #[test]
117    fn into_i32_works() {
118        let a: u8 = 200;
119        let v: i32 = a.into();
120        assert_eq!(v, 200);
121
122        let b: u16 = 4095;
123        let v: i32 = b.into();
124        assert_eq!(v, 4095);
125    }
126
127    #[test]
128    fn needs_u16_dispatch() {
129        assert!(!needs_u16(2));
130        assert!(!needs_u16(8));
131        assert!(needs_u16(9));
132        assert!(needs_u16(12));
133        assert!(needs_u16(16));
134    }
135}