/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 | | } |