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