image/codecs/jpeg/
decoder.rs

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