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