1 | use std::io::{BufRead, Seek}; |
2 | use std::marker::PhantomData; |
3 | |
4 | use crate::color::ColorType; |
5 | use crate::error::{ |
6 | DecodingError, ImageError, ImageResult, LimitError, UnsupportedError, UnsupportedErrorKind, |
7 | }; |
8 | use crate::image::{ImageDecoder, ImageFormat}; |
9 | use crate::metadata::Orientation; |
10 | use crate::Limits; |
11 | |
12 | type ZuneColorSpace = zune_core::colorspace::ColorSpace; |
13 | |
14 | /// JPEG decoder |
15 | pub 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 | // For API compatibility with the previous jpeg_decoder wrapper. |
23 | // Can be removed later, which would be an API break. |
24 | phantom: PhantomData<R>, |
25 | } |
26 | |
27 | impl<R: BufRead + Seek> JpegDecoder<R> { |
28 | /// Create a new decoder that decodes from the stream ```r``` |
29 | 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 | // now that we've decoded the headers we can `.unwrap()` |
40 | // all these functions that only fail if called before decoding the headers |
41 | let (width, height) = decoder.dimensions().unwrap(); |
42 | // JPEG can only express dimensions up to 65535x65535, so this conversion cannot fail |
43 | 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 | // Limits are disabled by default in the constructor for all decoders |
47 | 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 | |
60 | impl<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 | // `exif_metadata` caches the orientation, so call it if `orientation` hasn't been set yet. |
99 | 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 | |
138 | impl ColorType { |
139 | fn from_jpeg(colorspace: ZuneColorSpace) -> ColorType { |
140 | let colorspace: ColorSpace = to_supported_color_space(orig:colorspace); |
141 | use zune_core::colorspace::ColorSpace::*; |
142 | match colorspace { |
143 | // As of zune-jpeg 0.3.13 the output is always 8-bit, |
144 | // but support for 16-bit JPEG might be added in the future. |
145 | RGB => ColorType::Rgb8, |
146 | RGBA => ColorType::Rgba8, |
147 | Luma => ColorType::L8, |
148 | LumaA => ColorType::La8, |
149 | // to_supported_color_space() doesn't return any of the other variants |
150 | _ => unreachable!(), |
151 | } |
152 | } |
153 | } |
154 | |
155 | fn to_supported_color_space(orig: ZuneColorSpace) -> ZuneColorSpace { |
156 | use zune_core::colorspace::ColorSpace::*; |
157 | match orig { |
158 | RGB | RGBA | Luma | LumaA => orig, |
159 | // the rest is not supported by `image` so it will be converted to RGB during decoding |
160 | _ => RGB, |
161 | } |
162 | } |
163 | |
164 | fn new_zune_decoder( |
165 | input: &[u8], |
166 | orig_color_space: ZuneColorSpace, |
167 | limits: Limits, |
168 | ) -> zune_jpeg::JpegDecoder<&[u8]> { |
169 | let target_color_space: ColorSpace = to_supported_color_space(orig_color_space); |
170 | let mut options: DecoderOptions = zune_core::options::DecoderOptions::default() |
171 | .jpeg_set_out_colorspace(target_color_space) |
172 | .set_strict_mode(yes:false); |
173 | options = options.set_max_width(match limits.max_image_width { |
174 | Some(max_width: u32) => max_width as usize, // u32 to usize never truncates |
175 | None => usize::MAX, |
176 | }); |
177 | options = options.set_max_height(match limits.max_image_height { |
178 | Some(max_height: u32) => max_height as usize, // u32 to usize never truncates |
179 | None => usize::MAX, |
180 | }); |
181 | zune_jpeg::JpegDecoder::new_with_options(buf:input, options) |
182 | } |
183 | |
184 | impl ImageError { |
185 | fn from_jpeg(err: zune_jpeg::errors::DecodeErrors) -> ImageError { |
186 | use zune_jpeg::errors::DecodeErrors::*; |
187 | match err { |
188 | Unsupported(desc: UnsupportedSchemes) => ImageError::Unsupported(UnsupportedError::from_format_and_kind( |
189 | format:ImageFormat::Jpeg.into(), |
190 | kind:UnsupportedErrorKind::GenericFeature(format!(" {desc:?}" )), |
191 | )), |
192 | LargeDimensions(_) => ImageError::Limits(LimitError::from_kind( |
193 | crate::error::LimitErrorKind::DimensionError, |
194 | )), |
195 | err: DecodeErrors => ImageError::Decoding(DecodingError::new(format:ImageFormat::Jpeg.into(), err)), |
196 | } |
197 | } |
198 | } |
199 | |
200 | #[cfg (test)] |
201 | mod 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 | } |
212 | |