Skip to main content

dicom_toolkit_codec/jpeg/
mod.rs

1//! JPEG codec support — JPEG baseline/extended decoder and encoder.
2//!
3//! Wraps the `jpeg-decoder` and `jpeg-encoder` crates and adapts them to the
4//! DICOM encapsulated pixel data model.
5
6pub mod decoder;
7pub mod encoder;
8pub mod params;
9
10use dicom_toolkit_core::error::DcmResult;
11
12// ── Public re-exports ─────────────────────────────────────────────────────────
13
14pub use decoder::{decode_jpeg, JpegFrame};
15pub use encoder::encode_jpeg;
16pub use params::JpegParams;
17
18// ── JpegDecoder ───────────────────────────────────────────────────────────────
19
20/// A decoded JPEG frame with pixel data and dimensions.
21#[derive(Debug)]
22pub struct DecodedFrame {
23    /// Raw pixel bytes (grayscale L8, RGB24, or CMYK32).
24    pub pixels: Vec<u8>,
25    /// Frame width in pixels.
26    pub width: u32,
27    /// Frame height in pixels.
28    pub height: u32,
29    /// Number of colour components (1 = grayscale, 3 = RGB, 4 = CMYK).
30    pub components: u8,
31}
32
33/// JPEG codec for DICOM — decodes JPEG Baseline, Extended, and Lossless
34/// transfer syntaxes.
35///
36/// JPEG Lossless (PS3.4 Process 14) decoding depends on `jpeg-decoder`
37/// support; it may return [`dicom_toolkit_core::error::DcmError::DecompressionError`]
38/// if the lossless process is not supported by the library.
39pub struct JpegDecoder {
40    /// Transfer syntax UID this instance is associated with.
41    pub transfer_syntax_uid: &'static str,
42}
43
44impl JpegDecoder {
45    /// Decode a JPEG-compressed fragment to raw pixel bytes.
46    ///
47    /// Works for JPEG Baseline (1.2.840.10008.1.2.4.50),
48    /// JPEG Extended (…4.51), JPEG Lossless (…4.57, …4.70).
49    pub fn decode_frame(data: &[u8]) -> DcmResult<DecodedFrame> {
50        let frame = decode_jpeg(data)?;
51        Ok(DecodedFrame {
52            pixels: frame.data,
53            width: frame.width as u32,
54            height: frame.height as u32,
55            components: frame.samples_per_pixel,
56        })
57    }
58}
59
60// ── JPEG transfer syntax UIDs ─────────────────────────────────────────────────
61
62/// JPEG Baseline (Process 1) transfer syntax UID.
63pub const TS_JPEG_BASELINE: &str = "1.2.840.10008.1.2.4.50";
64/// JPEG Extended (Process 2 & 4) transfer syntax UID.
65pub const TS_JPEG_EXTENDED: &str = "1.2.840.10008.1.2.4.51";
66/// JPEG Lossless, Non-Hierarchical (Process 14) transfer syntax UID.
67pub const TS_JPEG_LOSSLESS: &str = "1.2.840.10008.1.2.4.57";
68/// JPEG Lossless, Selection Value 1 transfer syntax UID.
69pub const TS_JPEG_LOSSLESS_SV1: &str = "1.2.840.10008.1.2.4.70";
70
71// ── Tests ─────────────────────────────────────────────────────────────────────
72
73#[cfg(test)]
74mod tests {
75    use super::*;
76    use crate::jpeg::encoder::encode_jpeg;
77    use crate::jpeg::params::JpegParams;
78
79    #[test]
80    fn jpeg_decode_baseline() {
81        // Encode a small image and immediately decode it to verify the
82        // JpegDecoder::decode_frame surface.
83        let width = 8u16;
84        let height = 8u16;
85        let pixels: Vec<u8> = (0u8..64).collect();
86        let params = JpegParams {
87            quality: 90,
88            ..Default::default()
89        };
90        let compressed = encode_jpeg(&pixels, width, height, 1, &params).unwrap();
91
92        let frame = JpegDecoder::decode_frame(&compressed).unwrap();
93        assert_eq!(frame.width, width as u32);
94        assert_eq!(frame.height, height as u32);
95        assert_eq!(frame.components, 1);
96        assert_eq!(frame.pixels.len(), (width as usize) * (height as usize));
97    }
98}