Skip to main content

justpdf_core/stream/
dct.rs

1use crate::error::{JustPdfError, Result};
2
3/// Decode DCTDecode (JPEG) data.
4/// Returns raw pixel data (RGB or Grayscale).
5pub fn decode(data: &[u8]) -> Result<DecodedImage> {
6    let mut decoder = jpeg_decoder::Decoder::new(data);
7    let pixels = decoder.decode().map_err(|e| JustPdfError::StreamDecode {
8        filter: "DCTDecode".into(),
9        detail: format!("JPEG decode error: {e}"),
10    })?;
11
12    let info = decoder.info().ok_or_else(|| JustPdfError::StreamDecode {
13        filter: "DCTDecode".into(),
14        detail: "no JPEG image info".into(),
15    })?;
16
17    let color_type = match info.pixel_format {
18        jpeg_decoder::PixelFormat::L8 => ColorType::Gray,
19        jpeg_decoder::PixelFormat::RGB24 => ColorType::Rgb,
20        jpeg_decoder::PixelFormat::CMYK32 => ColorType::Cmyk,
21        jpeg_decoder::PixelFormat::L16 => ColorType::Gray,
22    };
23
24    Ok(DecodedImage {
25        width: info.width as u32,
26        height: info.height as u32,
27        color_type,
28        data: pixels,
29    })
30}
31
32/// Get JPEG image dimensions without fully decoding.
33pub fn jpeg_dimensions(data: &[u8]) -> Result<(u32, u32)> {
34    let mut decoder = jpeg_decoder::Decoder::new(data);
35    decoder
36        .read_info()
37        .map_err(|e| JustPdfError::StreamDecode {
38            filter: "DCTDecode".into(),
39            detail: format!("JPEG header error: {e}"),
40        })?;
41    let info = decoder.info().ok_or_else(|| JustPdfError::StreamDecode {
42        filter: "DCTDecode".into(),
43        detail: "no JPEG image info".into(),
44    })?;
45    Ok((info.width as u32, info.height as u32))
46}
47
48/// Color type of a decoded image.
49#[derive(Debug, Clone, Copy, PartialEq, Eq)]
50pub enum ColorType {
51    Gray,
52    Rgb,
53    Cmyk,
54}
55
56impl ColorType {
57    pub fn components(&self) -> usize {
58        match self {
59            Self::Gray => 1,
60            Self::Rgb => 3,
61            Self::Cmyk => 4,
62        }
63    }
64}
65
66/// A decoded image with pixel data.
67#[derive(Debug, Clone)]
68pub struct DecodedImage {
69    pub width: u32,
70    pub height: u32,
71    pub color_type: ColorType,
72    pub data: Vec<u8>,
73}
74
75#[cfg(test)]
76mod tests {
77    use super::*;
78
79    #[test]
80    fn test_corrupted_jpeg() {
81        let result = decode(b"\x00\x01\x02\x03");
82        assert!(result.is_err());
83    }
84
85    // Note: a real JPEG decode test would require a JPEG fixture file.
86    // The minimal JPEG is ~600 bytes so we don't embed one here.
87    // Integration tests with real PDFs cover this.
88}