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
14pub 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 phantom: PhantomData<R>,
25}
26
27impl<R: BufRead + Seek> JpegDecoder<R> {
28 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 let (width, height) = decoder.dimensions().unwrap();
42 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 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 options = zune_core::options::DecoderOptions::default()
71 .set_strict_mode(false)
72 .set_max_width(usize::MAX)
73 .set_max_height(usize::MAX);
74 let mut decoder = zune_jpeg::JpegDecoder::new_with_options(&self.input, options);
75 decoder.decode_headers().map_err(ImageError::from_jpeg)?;
76 Ok(decoder.icc_profile())
77 }
78
79 fn exif_metadata(&mut self) -> ImageResult<Option<Vec<u8>>> {
80 let options = zune_core::options::DecoderOptions::default()
81 .set_strict_mode(false)
82 .set_max_width(usize::MAX)
83 .set_max_height(usize::MAX);
84 let mut decoder = zune_jpeg::JpegDecoder::new_with_options(&self.input, options);
85 decoder.decode_headers().map_err(ImageError::from_jpeg)?;
86 let exif = decoder.exif().cloned();
87
88 self.orientation = Some(
89 exif.as_ref()
90 .and_then(|exif| Orientation::from_exif_chunk(exif))
91 .unwrap_or(Orientation::NoTransforms),
92 );
93
94 Ok(exif)
95 }
96
97 fn orientation(&mut self) -> ImageResult<Orientation> {
98 if self.orientation.is_none() {
100 let _ = self.exif_metadata()?;
101 }
102 Ok(self.orientation.unwrap())
103 }
104
105 fn read_image(self, buf: &mut [u8]) -> ImageResult<()> {
106 let advertised_len = self.total_bytes();
107 let actual_len = buf.len() as u64;
108
109 if actual_len != advertised_len {
110 return Err(ImageError::Decoding(DecodingError::new(
111 ImageFormat::Jpeg.into(),
112 format!(
113 "Length of the decoded data {actual_len} \
114 doesn't match the advertised dimensions of the image \
115 that imply length {advertised_len}"
116 ),
117 )));
118 }
119
120 let mut decoder = new_zune_decoder(&self.input, self.orig_color_space, self.limits);
121 decoder.decode_into(buf).map_err(ImageError::from_jpeg)?;
122 Ok(())
123 }
124
125 fn set_limits(&mut self, limits: Limits) -> ImageResult<()> {
126 limits.check_support(&crate::LimitSupport::default())?;
127 let (width, height) = self.dimensions();
128 limits.check_dimensions(width, height)?;
129 self.limits = limits;
130 Ok(())
131 }
132
133 fn read_image_boxed(self: Box<Self>, buf: &mut [u8]) -> ImageResult<()> {
134 (*self).read_image(buf)
135 }
136}
137
138impl ColorType {
139 fn from_jpeg(colorspace: ZuneColorSpace) -> ColorType {
140 let colorspace = to_supported_color_space(colorspace);
141 use zune_core::colorspace::ColorSpace::*;
142 match colorspace {
143 RGB => ColorType::Rgb8,
146 RGBA => ColorType::Rgba8,
147 Luma => ColorType::L8,
148 LumaA => ColorType::La8,
149 _ => unreachable!(),
151 }
152 }
153}
154
155fn to_supported_color_space(orig: ZuneColorSpace) -> ZuneColorSpace {
156 use zune_core::colorspace::ColorSpace::*;
157 match orig {
158 RGB | RGBA | Luma | LumaA => orig,
159 _ => RGB,
161 }
162}
163
164fn new_zune_decoder(
165 input: &[u8],
166 orig_color_space: ZuneColorSpace,
167 limits: Limits,
168) -> zune_jpeg::JpegDecoder<&[u8]> {
169 let target_color_space = to_supported_color_space(orig_color_space);
170 let mut options = zune_core::options::DecoderOptions::default()
171 .jpeg_set_out_colorspace(target_color_space)
172 .set_strict_mode(false);
173 options = options.set_max_width(match limits.max_image_width {
174 Some(max_width) => max_width as usize, None => usize::MAX,
176 });
177 options = options.set_max_height(match limits.max_image_height {
178 Some(max_height) => max_height as usize, None => usize::MAX,
180 });
181 zune_jpeg::JpegDecoder::new_with_options(input, options)
182}
183
184impl ImageError {
185 fn from_jpeg(err: zune_jpeg::errors::DecodeErrors) -> ImageError {
186 use zune_jpeg::errors::DecodeErrors::*;
187 match err {
188 Unsupported(desc) => ImageError::Unsupported(UnsupportedError::from_format_and_kind(
189 ImageFormat::Jpeg.into(),
190 UnsupportedErrorKind::GenericFeature(format!("{desc:?}")),
191 )),
192 LargeDimensions(_) => ImageError::Limits(LimitError::from_kind(
193 crate::error::LimitErrorKind::DimensionError,
194 )),
195 err => ImageError::Decoding(DecodingError::new(ImageFormat::Jpeg.into(), err)),
196 }
197 }
198}
199
200#[cfg(test)]
201mod tests {
202 use super::*;
203 use std::{fs, io::Cursor};
204
205 #[test]
206 fn test_exif_orientation() {
207 let data = fs::read("tests/images/jpg/portrait_2.jpg").unwrap();
208 let mut decoder = JpegDecoder::new(Cursor::new(data)).unwrap();
209 assert_eq!(decoder.orientation().unwrap(), Orientation::FlipHorizontal);
210 }
211}