Skip to main content

dicom_toolkit_codec/jpeg/
mod.rs

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