Coverage Report

Created: 2026-01-19 07:25

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