image/codecs/jpeg/
decoder.rs

1use std::io::{BufRead, Seek};
2use std::marker::PhantomData;
3
4use crate::color::ColorType;
5use crate::error::{
6    DecodingError, ImageError, ImageResult, LimitError, UnsupportedError, UnsupportedErrorKind,
7};
8use crate::image::{ImageDecoder, ImageFormat};
9use crate::metadata::Orientation;
10use crate::Limits;
11
12type ZuneColorSpace = zune_core::colorspace::ColorSpace;
13
14/// JPEG decoder
15pub struct JpegDecoder<R> {
16    input: Vec<u8>,
17    orig_color_space: ZuneColorSpace,
18    width: u16,
19    height: u16,
20    limits: Limits,
21    orientation: Option<Orientation>,
22    // For API compatibility with the previous jpeg_decoder wrapper.
23    // Can be removed later, which would be an API break.
24    phantom: PhantomData<R>,
25}
26
27impl<R: BufRead + Seek> JpegDecoder<R> {
28    /// Create a new decoder that decodes from the stream ```r```
29    pub fn new(r: R) -> ImageResult<JpegDecoder<R>> {
30        let mut input = Vec::new();
31        let mut r = r;
32        r.read_to_end(&mut input)?;
33        let options = zune_core::options::DecoderOptions::default()
34            .set_strict_mode(false)
35            .set_max_width(usize::MAX)
36            .set_max_height(usize::MAX);
37        let mut decoder = zune_jpeg::JpegDecoder::new_with_options(input.as_slice(), options);
38        decoder.decode_headers().map_err(ImageError::from_jpeg)?;
39        // now that we've decoded the headers we can `.unwrap()`
40        // all these functions that only fail if called before decoding the headers
41        let (width, height) = decoder.dimensions().unwrap();
42        // JPEG can only express dimensions up to 65535x65535, so this conversion cannot fail
43        let width: u16 = width.try_into().unwrap();
44        let height: u16 = height.try_into().unwrap();
45        let orig_color_space = decoder.get_output_colorspace().unwrap();
46        // Limits are disabled by default in the constructor for all decoders
47        let limits = Limits::no_limits();
48        Ok(JpegDecoder {
49            input,
50            orig_color_space,
51            width,
52            height,
53            limits,
54            orientation: None,
55            phantom: PhantomData,
56        })
57    }
58}
59
60impl<R: BufRead + Seek> ImageDecoder for JpegDecoder<R> {
61    fn dimensions(&self) -> (u32, u32) {
62        (u32::from(self.width), u32::from(self.height))
63    }
64
65    fn color_type(&self) -> ColorType {
66        ColorType::from_jpeg(self.orig_color_space)
67    }
68
69    fn icc_profile(&mut self) -> ImageResult<Option<Vec<u8>>> {
70        let mut decoder = zune_jpeg::JpegDecoder::new(&self.input);
71        decoder.decode_headers().map_err(ImageError::from_jpeg)?;
72        Ok(decoder.icc_profile())
73    }
74
75    fn exif_metadata(&mut self) -> ImageResult<Option<Vec<u8>>> {
76        let mut decoder = zune_jpeg::JpegDecoder::new(&self.input);
77        decoder.decode_headers().map_err(ImageError::from_jpeg)?;
78        let exif = decoder.exif().cloned();
79
80        self.orientation = Some(
81            exif.as_ref()
82                .and_then(|exif| Orientation::from_exif_chunk(exif))
83                .unwrap_or(Orientation::NoTransforms),
84        );
85
86        Ok(exif)
87    }
88
89    fn orientation(&mut self) -> ImageResult<Orientation> {
90        // `exif_metadata` caches the orientation, so call it if `orientation` hasn't been set yet.
91        if self.orientation.is_none() {
92            let _ = self.exif_metadata()?;
93        }
94        Ok(self.orientation.unwrap())
95    }
96
97    fn read_image(self, buf: &mut [u8]) -> ImageResult<()> {
98        let advertised_len = self.total_bytes();
99        let actual_len = buf.len() as u64;
100
101        if actual_len != advertised_len {
102            return Err(ImageError::Decoding(DecodingError::new(
103                ImageFormat::Jpeg.into(),
104                format!(
105                    "Length of the decoded data {actual_len}\
106                    doesn't match the advertised dimensions of the image\
107                    that imply length {advertised_len}"
108                ),
109            )));
110        }
111
112        let mut decoder = new_zune_decoder(&self.input, self.orig_color_space, self.limits);
113        decoder.decode_into(buf).map_err(ImageError::from_jpeg)?;
114        Ok(())
115    }
116
117    fn set_limits(&mut self, limits: Limits) -> ImageResult<()> {
118        limits.check_support(&crate::LimitSupport::default())?;
119        let (width, height) = self.dimensions();
120        limits.check_dimensions(width, height)?;
121        self.limits = limits;
122        Ok(())
123    }
124
125    fn read_image_boxed(self: Box<Self>, buf: &mut [u8]) -> ImageResult<()> {
126        (*self).read_image(buf)
127    }
128}
129
130impl ColorType {
131    fn from_jpeg(colorspace: ZuneColorSpace) -> ColorType {
132        let colorspace = to_supported_color_space(colorspace);
133        use zune_core::colorspace::ColorSpace::*;
134        match colorspace {
135            // As of zune-jpeg 0.3.13 the output is always 8-bit,
136            // but support for 16-bit JPEG might be added in the future.
137            RGB => ColorType::Rgb8,
138            RGBA => ColorType::Rgba8,
139            Luma => ColorType::L8,
140            LumaA => ColorType::La8,
141            // to_supported_color_space() doesn't return any of the other variants
142            _ => unreachable!(),
143        }
144    }
145}
146
147fn to_supported_color_space(orig: ZuneColorSpace) -> ZuneColorSpace {
148    use zune_core::colorspace::ColorSpace::*;
149    match orig {
150        RGB | RGBA | Luma | LumaA => orig,
151        // the rest is not supported by `image` so it will be converted to RGB during decoding
152        _ => RGB,
153    }
154}
155
156fn new_zune_decoder(
157    input: &[u8],
158    orig_color_space: ZuneColorSpace,
159    limits: Limits,
160) -> zune_jpeg::JpegDecoder<&[u8]> {
161    let target_color_space = to_supported_color_space(orig_color_space);
162    let mut options = zune_core::options::DecoderOptions::default()
163        .jpeg_set_out_colorspace(target_color_space)
164        .set_strict_mode(false);
165    options = options.set_max_width(match limits.max_image_width {
166        Some(max_width) => max_width as usize, // u32 to usize never truncates
167        None => usize::MAX,
168    });
169    options = options.set_max_height(match limits.max_image_height {
170        Some(max_height) => max_height as usize, // u32 to usize never truncates
171        None => usize::MAX,
172    });
173    zune_jpeg::JpegDecoder::new_with_options(input, options)
174}
175
176impl ImageError {
177    fn from_jpeg(err: zune_jpeg::errors::DecodeErrors) -> ImageError {
178        use zune_jpeg::errors::DecodeErrors::*;
179        match err {
180            Unsupported(desc) => ImageError::Unsupported(UnsupportedError::from_format_and_kind(
181                ImageFormat::Jpeg.into(),
182                UnsupportedErrorKind::GenericFeature(format!("{desc:?}")),
183            )),
184            LargeDimensions(_) => ImageError::Limits(LimitError::from_kind(
185                crate::error::LimitErrorKind::DimensionError,
186            )),
187            err => ImageError::Decoding(DecodingError::new(ImageFormat::Jpeg.into(), err)),
188        }
189    }
190}
191
192#[cfg(test)]
193mod tests {
194    use super::*;
195    use std::{fs, io::Cursor};
196
197    #[test]
198    fn test_exif_orientation() {
199        let data = fs::read("tests/images/jpg/portrait_2.jpg").unwrap();
200        let mut decoder = JpegDecoder::new(Cursor::new(data)).unwrap();
201        assert_eq!(decoder.orientation().unwrap(), Orientation::FlipHorizontal);
202    }
203}