Skip to main content

dicom_toolkit_codec/jpeg_ls/
encoder.rs

1//! JPEG-LS encoder — pure Rust implementation.
2//!
3//! Encodes raw pixel data into JPEG-LS compressed bitstream.
4
5use dicom_toolkit_core::error::{DcmError, DcmResult};
6
7use super::marker;
8use super::params::{
9    compute_default, ColorTransform, DerivedTraits, InterleaveMode, JlsParameters, BASIC_RESET,
10};
11use super::sample::needs_u16;
12use super::scan::ScanEncoder;
13
14/// Encode raw pixels as a JPEG-LS compressed bitstream.
15///
16/// * `pixels` — raw pixel data in native byte order (LE for multi-byte)
17/// * `width`, `height` — image dimensions
18/// * `bits_per_sample` — 2–16
19/// * `components` — 1–4
20/// * `near` — 0 for lossless, >0 for near-lossless
21pub fn encode_jpeg_ls(
22    pixels: &[u8],
23    width: u32,
24    height: u32,
25    bits_per_sample: u8,
26    components: u8,
27    near: i32,
28) -> DcmResult<Vec<u8>> {
29    if !(2..=16).contains(&bits_per_sample) {
30        return Err(DcmError::CompressionError {
31            reason: format!("JPEG-LS: unsupported bit depth {bits_per_sample}"),
32        });
33    }
34    if components == 0 || components > 4 {
35        return Err(DcmError::CompressionError {
36            reason: format!("JPEG-LS: unsupported component count {components}"),
37        });
38    }
39
40    let max_val = (1i32 << bits_per_sample) - 1;
41    let defaults = compute_default(max_val, near);
42    let traits = DerivedTraits::new(max_val, near, BASIC_RESET);
43    let w = width as usize;
44    let h = height as usize;
45    let nc = components as usize;
46
47    // Build JlsParameters for header.
48    let interleave = if nc == 1 {
49        InterleaveMode::None
50    } else {
51        InterleaveMode::Line
52    };
53
54    let params = JlsParameters {
55        width,
56        height,
57        bits_per_sample,
58        components,
59        near,
60        interleave,
61        color_transform: ColorTransform::None,
62        ..Default::default()
63    };
64
65    // Write header.
66    let mut output = marker::write_header(&params);
67
68    if nc == 1 || interleave == InterleaveMode::None {
69        // Convert pixel bytes to i32 values.
70        let pixel_values = bytes_to_i32(pixels, w * h * nc, bits_per_sample)?;
71
72        if nc == 1 {
73            let mut encoder = ScanEncoder::new(traits, defaults.t1, defaults.t2, defaults.t3, w, h);
74            let scan_data = encoder.encode(&pixel_values)?;
75            output.extend_from_slice(&scan_data);
76        } else {
77            // ILV_NONE: separate scan per component.
78            for c in 0..nc {
79                let component_pixels: Vec<i32> =
80                    (0..(w * h)).map(|i| pixel_values[i * nc + c]).collect();
81
82                let mut encoder =
83                    ScanEncoder::new(traits, defaults.t1, defaults.t2, defaults.t3, w, h);
84                let scan_data = encoder.encode(&component_pixels)?;
85                output.extend_from_slice(&scan_data);
86
87                // Write additional SOS marker for subsequent scans.
88                if c + 1 < nc {
89                    // For simplicity, we write single-component SOS markers.
90                    // This is handled by writing the full bitstream in one go for now.
91                }
92            }
93        }
94    } else {
95        // ILV_LINE: interleave component lines.
96        let pixel_values = bytes_to_i32(pixels, w * h * nc, bits_per_sample)?;
97
98        // Re-arrange: from pixel-interleaved [R,G,B,R,G,B,...] to
99        // line-interleaved [R-line, G-line, B-line, R-line, G-line, B-line, ...]
100        let mut line_interleaved = Vec::with_capacity(w * h * nc);
101        for y in 0..h {
102            for c in 0..nc {
103                for x in 0..w {
104                    line_interleaved.push(pixel_values[(y * w + x) * nc + c]);
105                }
106            }
107        }
108
109        let effective_h = h * nc;
110        let mut encoder = ScanEncoder::new(
111            traits,
112            defaults.t1,
113            defaults.t2,
114            defaults.t3,
115            w,
116            effective_h,
117        );
118        let scan_data = encoder.encode(&line_interleaved)?;
119        output.extend_from_slice(&scan_data);
120    }
121
122    marker::write_eoi(&mut output);
123    Ok(output)
124}
125
126/// Convert raw pixel bytes to i32 values.
127fn bytes_to_i32(data: &[u8], count: usize, bits_per_sample: u8) -> DcmResult<Vec<i32>> {
128    if needs_u16(bits_per_sample) {
129        if data.len() < count * 2 {
130            return Err(DcmError::CompressionError {
131                reason: format!("JPEG-LS: expected {} bytes, got {}", count * 2, data.len()),
132            });
133        }
134        let mut values = Vec::with_capacity(count);
135        for i in 0..count {
136            let lo = data[i * 2] as i32;
137            let hi = data[i * 2 + 1] as i32;
138            values.push(lo | (hi << 8));
139        }
140        Ok(values)
141    } else {
142        if data.len() < count {
143            return Err(DcmError::CompressionError {
144                reason: format!("JPEG-LS: expected {} bytes, got {}", count, data.len()),
145            });
146        }
147        Ok(data[..count].iter().map(|&b| b as i32).collect())
148    }
149}
150
151// ── Tests ─────────────────────────────────────────────────────────────────────
152
153#[cfg(test)]
154mod tests {
155    use super::*;
156    use crate::jpeg_ls::decoder::decode_jpeg_ls;
157
158    #[test]
159    fn encode_decode_roundtrip_8bit_grayscale() {
160        let w = 16u32;
161        let h = 8u32;
162        let mut pixels = Vec::with_capacity((w * h) as usize);
163        for y in 0..h {
164            for x in 0..w {
165                pixels.push(((x * 16 + y * 8) % 256) as u8);
166            }
167        }
168
169        let encoded = encode_jpeg_ls(&pixels, w, h, 8, 1, 0).unwrap();
170        let decoded = decode_jpeg_ls(&encoded).unwrap();
171
172        assert_eq!(decoded.width, w);
173        assert_eq!(decoded.height, h);
174        assert_eq!(decoded.bits_per_sample, 8);
175        assert_eq!(decoded.components, 1);
176        assert_eq!(decoded.pixels, pixels);
177    }
178
179    #[test]
180    fn encode_decode_roundtrip_constant() {
181        let pixels = vec![128u8; 64];
182        let encoded = encode_jpeg_ls(&pixels, 8, 8, 8, 1, 0).unwrap();
183        let decoded = decode_jpeg_ls(&encoded).unwrap();
184        assert_eq!(decoded.pixels, pixels);
185    }
186
187    #[test]
188    fn encode_decode_roundtrip_16bit_grayscale() {
189        let w = 8u32;
190        let h = 4u32;
191        let mut pixels = Vec::with_capacity((w * h * 2) as usize);
192        for y in 0..h {
193            for x in 0..w {
194                let val = ((x * 1000 + y * 500) % 4096) as u16;
195                pixels.push(val as u8);
196                pixels.push((val >> 8) as u8);
197            }
198        }
199
200        let encoded = encode_jpeg_ls(&pixels, w, h, 12, 1, 0).unwrap();
201        let decoded = decode_jpeg_ls(&encoded).unwrap();
202
203        assert_eq!(decoded.width, w);
204        assert_eq!(decoded.height, h);
205        assert_eq!(decoded.bits_per_sample, 12);
206        assert_eq!(decoded.pixels, pixels);
207    }
208}