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, ¶ms).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}