image/codecs/jpeg/
decoder.rs1use 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::metadata::Orientation;
9use crate::{ImageDecoder, ImageFormat, Limits};
10
11type ZuneColorSpace = zune_core::colorspace::ColorSpace;
12
13pub struct JpegDecoder<R> {
15 input: Vec<u8>,
16 orig_color_space: ZuneColorSpace,
17 width: u16,
18 height: u16,
19 limits: Limits,
20 orientation: Option<Orientation>,
21 phantom: PhantomData<R>,
24}
25
26impl<R: BufRead + Seek> JpegDecoder<R> {
27 pub fn new(r: R) -> ImageResult<JpegDecoder<R>> {
29 let mut input = Vec::new();
30 let mut r = r;
31 r.read_to_end(&mut input)?;
32 let options = zune_core::options::DecoderOptions::default()
33 .set_strict_mode(false)
34 .set_max_width(usize::MAX)
35 .set_max_height(usize::MAX);
36 let mut decoder = zune_jpeg::JpegDecoder::new_with_options(input.as_slice(), options);
37 decoder.decode_headers().map_err(ImageError::from_jpeg)?;
38 let (width, height) = decoder.dimensions().unwrap();
41 let width: u16 = width.try_into().unwrap();
43 let height: u16 = height.try_into().unwrap();
44 let orig_color_space = decoder.get_output_colorspace().unwrap();
45 let limits = Limits::no_limits();
47 Ok(JpegDecoder {
48 input,
49 orig_color_space,
50 width,
51 height,
52 limits,
53 orientation: None,
54 phantom: PhantomData,
55 })
56 }
57}
58
59impl<R: BufRead + Seek> ImageDecoder for JpegDecoder<R> {
60 fn dimensions(&self) -> (u32, u32) {
61 (u32::from(self.width), u32::from(self.height))
62 }
63
64 fn color_type(&self) -> ColorType {
65 ColorType::from_jpeg(self.orig_color_space)
66 }
67
68 fn icc_profile(&mut self) -> ImageResult<Option<Vec<u8>>> {
69 let options = zune_core::options::DecoderOptions::default()
70 .set_strict_mode(false)
71 .set_max_width(usize::MAX)
72 .set_max_height(usize::MAX);
73 let mut decoder = zune_jpeg::JpegDecoder::new_with_options(&self.input, options);
74 decoder.decode_headers().map_err(ImageError::from_jpeg)?;
75 Ok(decoder.icc_profile())
76 }
77
78 fn exif_metadata(&mut self) -> ImageResult<Option<Vec<u8>>> {
79 let options = zune_core::options::DecoderOptions::default()
80 .set_strict_mode(false)
81 .set_max_width(usize::MAX)
82 .set_max_height(usize::MAX);
83 let mut decoder = zune_jpeg::JpegDecoder::new_with_options(&self.input, options);
84 decoder.decode_headers().map_err(ImageError::from_jpeg)?;
85 let exif = decoder.exif().cloned();
86
87 self.orientation = Some(
88 exif.as_ref()
89 .and_then(|exif| Orientation::from_exif_chunk(exif))
90 .unwrap_or(Orientation::NoTransforms),
91 );
92
93 Ok(exif)
94 }
95
96 fn orientation(&mut self) -> ImageResult<Orientation> {
97 if self.orientation.is_none() {
99 let _ = self.exif_metadata()?;
100 }
101 Ok(self.orientation.unwrap())
102 }
103
104 fn read_image(self, buf: &mut [u8]) -> ImageResult<()> {
105 let advertised_len = self.total_bytes();
106 let actual_len = buf.len() as u64;
107
108 if actual_len != advertised_len {
109 return Err(ImageError::Decoding(DecodingError::new(
110 ImageFormat::Jpeg.into(),
111 format!(
112 "Length of the decoded data {actual_len} \
113 doesn't match the advertised dimensions of the image \
114 that imply length {advertised_len}"
115 ),
116 )));
117 }
118
119 let mut decoder = new_zune_decoder(&self.input, self.orig_color_space, self.limits);
120 decoder.decode_into(buf).map_err(ImageError::from_jpeg)?;
121 Ok(())
122 }
123
124 fn set_limits(&mut self, limits: Limits) -> ImageResult<()> {
125 limits.check_support(&crate::LimitSupport::default())?;
126 let (width, height) = self.dimensions();
127 limits.check_dimensions(width, height)?;
128 self.limits = limits;
129 Ok(())
130 }
131
132 fn read_image_boxed(self: Box<Self>, buf: &mut [u8]) -> ImageResult<()> {
133 (*self).read_image(buf)
134 }
135}
136
137impl ColorType {
138 fn from_jpeg(colorspace: ZuneColorSpace) -> ColorType {
139 let colorspace = to_supported_color_space(colorspace);
140 use zune_core::colorspace::ColorSpace::*;
141 match colorspace {
142 RGB => ColorType::Rgb8,
145 RGBA => ColorType::Rgba8,
146 Luma => ColorType::L8,
147 LumaA => ColorType::La8,
148 _ => unreachable!(),
150 }
151 }
152}
153
154fn to_supported_color_space(orig: ZuneColorSpace) -> ZuneColorSpace {
155 use zune_core::colorspace::ColorSpace::*;
156 match orig {
157 RGB | RGBA | Luma | LumaA => orig,
158 _ => RGB,
160 }
161}
162
163fn new_zune_decoder(
164 input: &[u8],
165 orig_color_space: ZuneColorSpace,
166 limits: Limits,
167) -> zune_jpeg::JpegDecoder<&[u8]> {
168 let target_color_space = to_supported_color_space(orig_color_space);
169 let mut options = zune_core::options::DecoderOptions::default()
170 .jpeg_set_out_colorspace(target_color_space)
171 .set_strict_mode(false);
172 options = options.set_max_width(match limits.max_image_width {
173 Some(max_width) => max_width as usize, None => usize::MAX,
175 });
176 options = options.set_max_height(match limits.max_image_height {
177 Some(max_height) => max_height as usize, None => usize::MAX,
179 });
180 zune_jpeg::JpegDecoder::new_with_options(input, options)
181}
182
183impl ImageError {
184 fn from_jpeg(err: zune_jpeg::errors::DecodeErrors) -> ImageError {
185 use zune_jpeg::errors::DecodeErrors::*;
186 match err {
187 Unsupported(desc) => ImageError::Unsupported(UnsupportedError::from_format_and_kind(
188 ImageFormat::Jpeg.into(),
189 UnsupportedErrorKind::GenericFeature(format!("{desc:?}")),
190 )),
191 LargeDimensions(_) => ImageError::Limits(LimitError::from_kind(
192 crate::error::LimitErrorKind::DimensionError,
193 )),
194 err => ImageError::Decoding(DecodingError::new(ImageFormat::Jpeg.into(), err)),
195 }
196 }
197}
198
199#[cfg(test)]
200mod tests {
201 use super::*;
202 use std::{fs, io::Cursor};
203
204 #[test]
205 fn test_exif_orientation() {
206 let data = fs::read("tests/images/jpg/portrait_2.jpg").unwrap();
207 let mut decoder = JpegDecoder::new(Cursor::new(data)).unwrap();
208 assert_eq!(decoder.orientation().unwrap(), Orientation::FlipHorizontal);
209 }
210}