/src/image/src/codecs/png.rs
Line | Count | Source |
1 | | //! Decoding and Encoding of PNG Images |
2 | | //! |
3 | | //! PNG (Portable Network Graphics) is an image format that supports lossless compression. |
4 | | //! |
5 | | //! # Related Links |
6 | | //! * <http://www.w3.org/TR/PNG/> - The PNG Specification |
7 | | |
8 | | use std::borrow::Cow; |
9 | | use std::io::{BufRead, Seek, Write}; |
10 | | |
11 | | use png::{BlendOp, DeflateCompression, DisposeOp}; |
12 | | |
13 | | use crate::animation::{Delay, Frame, Frames, Ratio}; |
14 | | use crate::color::{Blend, ColorType, ExtendedColorType}; |
15 | | use crate::error::{ |
16 | | DecodingError, ImageError, ImageResult, LimitError, LimitErrorKind, ParameterError, |
17 | | ParameterErrorKind, UnsupportedError, UnsupportedErrorKind, |
18 | | }; |
19 | | use crate::utils::vec_try_with_capacity; |
20 | | use crate::{ |
21 | | AnimationDecoder, DynamicImage, GenericImage, GenericImageView, ImageBuffer, ImageDecoder, |
22 | | ImageEncoder, ImageFormat, Limits, Luma, LumaA, Rgb, Rgba, RgbaImage, |
23 | | }; |
24 | | |
25 | | // http://www.w3.org/TR/PNG-Structure.html |
26 | | // The first eight bytes of a PNG file always contain the following (decimal) values: |
27 | | pub(crate) const PNG_SIGNATURE: [u8; 8] = [137, 80, 78, 71, 13, 10, 26, 10]; |
28 | | const XMP_KEY: &str = "XML:com.adobe.xmp"; |
29 | | const IPTC_KEYS: &[&str] = &["Raw profile type iptc", "Raw profile type 8bim"]; |
30 | | |
31 | | /// PNG decoder |
32 | | pub struct PngDecoder<R: BufRead + Seek> { |
33 | | color_type: ColorType, |
34 | | reader: png::Reader<R>, |
35 | | limits: Limits, |
36 | | } |
37 | | |
38 | | impl<R: BufRead + Seek> PngDecoder<R> { |
39 | | /// Creates a new decoder that decodes from the stream ```r``` |
40 | 0 | pub fn new(r: R) -> ImageResult<PngDecoder<R>> { |
41 | 0 | Self::with_limits(r, Limits::no_limits()) |
42 | 0 | } |
43 | | |
44 | | /// Creates a new decoder that decodes from the stream ```r``` with the given limits. |
45 | 11.6k | pub fn with_limits(r: R, limits: Limits) -> ImageResult<PngDecoder<R>> { |
46 | 11.6k | limits.check_support(&crate::LimitSupport::default())?; |
47 | | |
48 | 11.6k | let max_bytes = usize::try_from(limits.max_alloc.unwrap_or(u64::MAX)).unwrap_or(usize::MAX); |
49 | 11.6k | let mut decoder = png::Decoder::new_with_limits(r, png::Limits { bytes: max_bytes }); |
50 | 11.6k | decoder.set_ignore_text_chunk(false); |
51 | | |
52 | 11.6k | let info = decoder.read_header_info().map_err(ImageError::from_png)?; |
53 | 10.9k | limits.check_dimensions(info.width, info.height)?; |
54 | | |
55 | | // By default the PNG decoder will scale 16 bpc to 8 bpc, so custom |
56 | | // transformations must be set. EXPAND preserves the default behavior |
57 | | // expanding bpc < 8 to 8 bpc. |
58 | 10.8k | decoder.set_transformations(png::Transformations::EXPAND); |
59 | 10.8k | let reader = decoder.read_info().map_err(ImageError::from_png)?; |
60 | 5.93k | let (color_type, bits) = reader.output_color_type(); |
61 | 5.93k | let color_type = match (color_type, bits) { |
62 | 1.33k | (png::ColorType::Grayscale, png::BitDepth::Eight) => ColorType::L8, |
63 | 426 | (png::ColorType::Grayscale, png::BitDepth::Sixteen) => ColorType::L16, |
64 | 306 | (png::ColorType::GrayscaleAlpha, png::BitDepth::Eight) => ColorType::La8, |
65 | 326 | (png::ColorType::GrayscaleAlpha, png::BitDepth::Sixteen) => ColorType::La16, |
66 | 789 | (png::ColorType::Rgb, png::BitDepth::Eight) => ColorType::Rgb8, |
67 | 336 | (png::ColorType::Rgb, png::BitDepth::Sixteen) => ColorType::Rgb16, |
68 | 2.12k | (png::ColorType::Rgba, png::BitDepth::Eight) => ColorType::Rgba8, |
69 | 290 | (png::ColorType::Rgba, png::BitDepth::Sixteen) => ColorType::Rgba16, |
70 | | |
71 | | (png::ColorType::Grayscale, png::BitDepth::One) => { |
72 | 0 | return Err(unsupported_color(ExtendedColorType::L1)) |
73 | | } |
74 | | (png::ColorType::GrayscaleAlpha, png::BitDepth::One) => { |
75 | 0 | return Err(unsupported_color(ExtendedColorType::La1)) |
76 | | } |
77 | | (png::ColorType::Rgb, png::BitDepth::One) => { |
78 | 0 | return Err(unsupported_color(ExtendedColorType::Rgb1)) |
79 | | } |
80 | | (png::ColorType::Rgba, png::BitDepth::One) => { |
81 | 0 | return Err(unsupported_color(ExtendedColorType::Rgba1)) |
82 | | } |
83 | | |
84 | | (png::ColorType::Grayscale, png::BitDepth::Two) => { |
85 | 0 | return Err(unsupported_color(ExtendedColorType::L2)) |
86 | | } |
87 | | (png::ColorType::GrayscaleAlpha, png::BitDepth::Two) => { |
88 | 0 | return Err(unsupported_color(ExtendedColorType::La2)) |
89 | | } |
90 | | (png::ColorType::Rgb, png::BitDepth::Two) => { |
91 | 0 | return Err(unsupported_color(ExtendedColorType::Rgb2)) |
92 | | } |
93 | | (png::ColorType::Rgba, png::BitDepth::Two) => { |
94 | 0 | return Err(unsupported_color(ExtendedColorType::Rgba2)) |
95 | | } |
96 | | |
97 | | (png::ColorType::Grayscale, png::BitDepth::Four) => { |
98 | 0 | return Err(unsupported_color(ExtendedColorType::L4)) |
99 | | } |
100 | | (png::ColorType::GrayscaleAlpha, png::BitDepth::Four) => { |
101 | 0 | return Err(unsupported_color(ExtendedColorType::La4)) |
102 | | } |
103 | | (png::ColorType::Rgb, png::BitDepth::Four) => { |
104 | 0 | return Err(unsupported_color(ExtendedColorType::Rgb4)) |
105 | | } |
106 | | (png::ColorType::Rgba, png::BitDepth::Four) => { |
107 | 0 | return Err(unsupported_color(ExtendedColorType::Rgba4)) |
108 | | } |
109 | | |
110 | 0 | (png::ColorType::Indexed, bits) => { |
111 | 0 | return Err(unsupported_color(ExtendedColorType::Unknown(bits as u8))) |
112 | | } |
113 | | }; |
114 | | |
115 | 5.93k | Ok(PngDecoder { |
116 | 5.93k | color_type, |
117 | 5.93k | reader, |
118 | 5.93k | limits, |
119 | 5.93k | }) |
120 | 11.6k | } |
121 | | |
122 | | /// Returns the gamma value of the image or None if no gamma value is indicated. |
123 | | /// |
124 | | /// If an sRGB chunk is present this method returns a gamma value of 0.45455 and ignores the |
125 | | /// value in the gAMA chunk. This is the recommended behavior according to the PNG standard: |
126 | | /// |
127 | | /// > When the sRGB chunk is present, [...] decoders that recognize the sRGB chunk but are not |
128 | | /// > capable of colour management are recommended to ignore the gAMA and cHRM chunks, and use |
129 | | /// > the values given above as if they had appeared in gAMA and cHRM chunks. |
130 | 0 | pub fn gamma_value(&self) -> ImageResult<Option<f64>> { |
131 | 0 | Ok(self |
132 | 0 | .reader |
133 | 0 | .info() |
134 | 0 | .source_gamma |
135 | 0 | .map(|x| f64::from(x.into_scaled()) / 100_000.0)) |
136 | 0 | } |
137 | | |
138 | | /// Turn this into an iterator over the animation frames. |
139 | | /// |
140 | | /// Reading the complete animation requires more memory than reading the data from the IDAT |
141 | | /// frame–multiple frame buffers need to be reserved at the same time. We further do not |
142 | | /// support compositing 16-bit colors. In any case this would be lossy as the interface of |
143 | | /// animation decoders does not support 16-bit colors. |
144 | | /// |
145 | | /// If something is not supported or a limit is violated then the decoding step that requires |
146 | | /// them will fail and an error will be returned instead of the frame. No further frames will |
147 | | /// be returned. |
148 | 0 | pub fn apng(self) -> ImageResult<ApngDecoder<R>> { |
149 | 0 | Ok(ApngDecoder::new(self)) |
150 | 0 | } |
151 | | |
152 | | /// Returns if the image contains an animation. |
153 | | /// |
154 | | /// Note that the file itself decides if the default image is considered to be part of the |
155 | | /// animation. When it is not the common interpretation is to use it as a thumbnail. |
156 | | /// |
157 | | /// If a non-animated image is converted into an `ApngDecoder` then its iterator is empty. |
158 | 0 | pub fn is_apng(&self) -> ImageResult<bool> { |
159 | 0 | Ok(self.reader.info().animation_control.is_some()) |
160 | 0 | } |
161 | | } |
162 | | |
163 | 0 | fn unsupported_color(ect: ExtendedColorType) -> ImageError { |
164 | 0 | ImageError::Unsupported(UnsupportedError::from_format_and_kind( |
165 | 0 | ImageFormat::Png.into(), |
166 | 0 | UnsupportedErrorKind::Color(ect), |
167 | 0 | )) |
168 | 0 | } |
169 | | |
170 | | impl<R: BufRead + Seek> ImageDecoder for PngDecoder<R> { |
171 | 28.8k | fn dimensions(&self) -> (u32, u32) { |
172 | 28.8k | self.reader.info().size() |
173 | 28.8k | } |
174 | | |
175 | 27.3k | fn color_type(&self) -> ColorType { |
176 | 27.3k | self.color_type |
177 | 27.3k | } |
178 | | |
179 | 0 | fn icc_profile(&mut self) -> ImageResult<Option<Vec<u8>>> { |
180 | 0 | Ok(self.reader.info().icc_profile.as_ref().map(|x| x.to_vec())) |
181 | 0 | } |
182 | | |
183 | 0 | fn exif_metadata(&mut self) -> ImageResult<Option<Vec<u8>>> { |
184 | 0 | Ok(self |
185 | 0 | .reader |
186 | 0 | .info() |
187 | 0 | .exif_metadata |
188 | 0 | .as_ref() |
189 | 0 | .map(|x| x.to_vec())) |
190 | 0 | } |
191 | | |
192 | 0 | fn xmp_metadata(&mut self) -> ImageResult<Option<Vec<u8>>> { |
193 | 0 | if let Some(mut itx_chunk) = self |
194 | 0 | .reader |
195 | 0 | .info() |
196 | 0 | .utf8_text |
197 | 0 | .iter() |
198 | 0 | .find(|chunk| chunk.keyword.contains(XMP_KEY)) |
199 | 0 | .cloned() |
200 | | { |
201 | 0 | itx_chunk.decompress_text().map_err(ImageError::from_png)?; |
202 | 0 | return itx_chunk |
203 | 0 | .get_text() |
204 | 0 | .map(|text| Some(text.as_bytes().to_vec())) |
205 | 0 | .map_err(ImageError::from_png); |
206 | 0 | } |
207 | 0 | Ok(None) |
208 | 0 | } |
209 | | |
210 | 0 | fn iptc_metadata(&mut self) -> ImageResult<Option<Vec<u8>>> { |
211 | 0 | if let Some(mut text_chunk) = self |
212 | 0 | .reader |
213 | 0 | .info() |
214 | 0 | .compressed_latin1_text |
215 | 0 | .iter() |
216 | 0 | .find(|chunk| IPTC_KEYS.iter().any(|key| chunk.keyword.contains(key))) |
217 | 0 | .cloned() |
218 | | { |
219 | 0 | text_chunk.decompress_text().map_err(ImageError::from_png)?; |
220 | 0 | return text_chunk |
221 | 0 | .get_text() |
222 | 0 | .map(|text| Some(text.as_bytes().to_vec())) |
223 | 0 | .map_err(ImageError::from_png); |
224 | 0 | } |
225 | | |
226 | 0 | if let Some(text_chunk) = self |
227 | 0 | .reader |
228 | 0 | .info() |
229 | 0 | .uncompressed_latin1_text |
230 | 0 | .iter() |
231 | 0 | .find(|chunk| IPTC_KEYS.iter().any(|key| chunk.keyword.contains(key))) |
232 | 0 | .cloned() |
233 | | { |
234 | 0 | return Ok(Some(text_chunk.text.into_bytes())); |
235 | 0 | } |
236 | 0 | Ok(None) |
237 | 0 | } |
238 | | |
239 | 5.82k | fn read_image(mut self, buf: &mut [u8]) -> ImageResult<()> { |
240 | | use byteorder_lite::{BigEndian, ByteOrder, NativeEndian}; |
241 | | |
242 | 5.82k | assert_eq!(u64::try_from(buf.len()), Ok(self.total_bytes())); |
243 | 5.82k | self.reader.next_frame(buf).map_err(ImageError::from_png)?; |
244 | | // PNG images are big endian. For 16 bit per channel and larger types, |
245 | | // the buffer may need to be reordered to native endianness per the |
246 | | // contract of `read_image`. |
247 | | // TODO: assumes equal channel bit depth. |
248 | 125 | let bpc = self.color_type().bytes_per_pixel() / self.color_type().channel_count(); |
249 | | |
250 | 125 | match bpc { |
251 | 71 | 1 => (), // No reodering necessary for u8 |
252 | 185M | 2 => buf.chunks_exact_mut(2).for_each(|c| { |
253 | 185M | let v = BigEndian::read_u16(c); |
254 | 185M | NativeEndian::write_u16(c, v); |
255 | 185M | }), |
256 | 0 | _ => unreachable!(), |
257 | | } |
258 | 125 | Ok(()) |
259 | 5.82k | } |
260 | | |
261 | 5.82k | fn read_image_boxed(self: Box<Self>, buf: &mut [u8]) -> ImageResult<()> { |
262 | 5.82k | (*self).read_image(buf) |
263 | 5.82k | } |
264 | | |
265 | 4.05k | fn set_limits(&mut self, limits: Limits) -> ImageResult<()> { |
266 | 4.05k | limits.check_support(&crate::LimitSupport::default())?; |
267 | 4.05k | let info = self.reader.info(); |
268 | 4.05k | limits.check_dimensions(info.width, info.height)?; |
269 | 4.05k | self.limits = limits; |
270 | | // TODO: add `png::Reader::change_limits()` and call it here |
271 | | // to also constrain the internal buffer allocations in the PNG crate |
272 | 4.05k | Ok(()) |
273 | 4.05k | } |
274 | | } |
275 | | |
276 | | /// An [`AnimationDecoder`] adapter of [`PngDecoder`]. |
277 | | /// |
278 | | /// See [`PngDecoder::apng`] for more information. |
279 | | /// |
280 | | /// [`AnimationDecoder`]: ../trait.AnimationDecoder.html |
281 | | /// [`PngDecoder`]: struct.PngDecoder.html |
282 | | /// [`PngDecoder::apng`]: struct.PngDecoder.html#method.apng |
283 | | pub struct ApngDecoder<R: BufRead + Seek> { |
284 | | inner: PngDecoder<R>, |
285 | | /// The current output buffer. |
286 | | current: Option<RgbaImage>, |
287 | | /// The previous output buffer, used for dispose op previous. |
288 | | previous: Option<RgbaImage>, |
289 | | /// The dispose op of the current frame. |
290 | | dispose: DisposeOp, |
291 | | |
292 | | /// The region to dispose of the previous frame. |
293 | | dispose_region: Option<(u32, u32, u32, u32)>, |
294 | | /// The number of image still expected to be able to load. |
295 | | remaining: u32, |
296 | | /// The next (first) image is the thumbnail. |
297 | | has_thumbnail: bool, |
298 | | } |
299 | | |
300 | | impl<R: BufRead + Seek> ApngDecoder<R> { |
301 | 0 | fn new(inner: PngDecoder<R>) -> Self { |
302 | 0 | let info = inner.reader.info(); |
303 | 0 | let remaining = match info.animation_control() { |
304 | | // The expected number of fcTL in the remaining image. |
305 | 0 | Some(actl) => actl.num_frames, |
306 | 0 | None => 0, |
307 | | }; |
308 | | // If the IDAT has no fcTL then it is not part of the animation counted by |
309 | | // num_frames. All following fdAT chunks must be preceded by an fcTL |
310 | 0 | let has_thumbnail = info.frame_control.is_none(); |
311 | 0 | ApngDecoder { |
312 | 0 | inner, |
313 | 0 | current: None, |
314 | 0 | previous: None, |
315 | 0 | dispose: DisposeOp::Background, |
316 | 0 | dispose_region: None, |
317 | 0 | remaining, |
318 | 0 | has_thumbnail, |
319 | 0 | } |
320 | 0 | } |
321 | | |
322 | | // TODO: thumbnail(&mut self) -> Option<impl ImageDecoder<'_>> |
323 | | |
324 | | /// Decode one subframe and overlay it on the canvas. |
325 | 0 | fn mix_next_frame(&mut self) -> Result<Option<&RgbaImage>, ImageError> { |
326 | | // The iterator always produces RGBA8 images |
327 | | const COLOR_TYPE: ColorType = ColorType::Rgba8; |
328 | | |
329 | | // Allocate the buffers, honoring the memory limits |
330 | 0 | let (width, height) = self.inner.dimensions(); |
331 | | { |
332 | 0 | let limits = &mut self.inner.limits; |
333 | 0 | if self.previous.is_none() { |
334 | 0 | limits.reserve_buffer(width, height, COLOR_TYPE)?; |
335 | 0 | self.previous = Some(RgbaImage::new(width, height)); |
336 | 0 | } |
337 | | |
338 | 0 | if self.current.is_none() { |
339 | 0 | limits.reserve_buffer(width, height, COLOR_TYPE)?; |
340 | 0 | self.current = Some(RgbaImage::new(width, height)); |
341 | 0 | } |
342 | | } |
343 | | |
344 | | // Remove this image from remaining. |
345 | 0 | self.remaining = match self.remaining.checked_sub(1) { |
346 | 0 | None => return Ok(None), |
347 | 0 | Some(next) => next, |
348 | | }; |
349 | | |
350 | | // Shorten ourselves to 0 in case of error. |
351 | 0 | let remaining = self.remaining; |
352 | 0 | self.remaining = 0; |
353 | | |
354 | | // Skip the thumbnail that is not part of the animation. |
355 | 0 | if self.has_thumbnail { |
356 | | // Clone the limits so that our one-off allocation that's destroyed after this scope doesn't persist |
357 | 0 | let mut limits = self.inner.limits.clone(); |
358 | | |
359 | 0 | let buffer_size = self.inner.reader.output_buffer_size().ok_or_else(|| { |
360 | 0 | ImageError::Limits(LimitError::from_kind(LimitErrorKind::InsufficientMemory)) |
361 | 0 | })?; |
362 | | |
363 | 0 | limits.reserve_usize(buffer_size)?; |
364 | 0 | let mut buffer = vec![0; buffer_size]; |
365 | | // TODO: add `png::Reader::change_limits()` and call it here |
366 | | // to also constrain the internal buffer allocations in the PNG crate |
367 | 0 | self.inner |
368 | 0 | .reader |
369 | 0 | .next_frame(&mut buffer) |
370 | 0 | .map_err(ImageError::from_png)?; |
371 | 0 | self.has_thumbnail = false; |
372 | 0 | } |
373 | | |
374 | 0 | self.animatable_color_type()?; |
375 | | |
376 | | // We've initialized them earlier in this function |
377 | 0 | let previous = self.previous.as_mut().unwrap(); |
378 | 0 | let current = self.current.as_mut().unwrap(); |
379 | | |
380 | | // Dispose of the previous frame. |
381 | | |
382 | 0 | match self.dispose { |
383 | 0 | DisposeOp::None => { |
384 | 0 | previous.clone_from(current); |
385 | 0 | } |
386 | | DisposeOp::Background => { |
387 | 0 | previous.clone_from(current); |
388 | 0 | if let Some((px, py, width, height)) = self.dispose_region { |
389 | 0 | let mut region_current = current.sub_image(px, py, width, height); |
390 | | |
391 | | // FIXME: This is a workaround for the fact that `pixels_mut` is not implemented |
392 | 0 | let pixels: Vec<_> = region_current.pixels().collect(); |
393 | | |
394 | 0 | for (x, y, _) in &pixels { |
395 | 0 | region_current.put_pixel(*x, *y, Rgba::from([0, 0, 0, 0])); |
396 | 0 | } |
397 | | } else { |
398 | | // The first frame is always a background frame. |
399 | 0 | current.pixels_mut().for_each(|pixel| { |
400 | 0 | *pixel = Rgba::from([0, 0, 0, 0]); |
401 | 0 | }); |
402 | | } |
403 | | } |
404 | 0 | DisposeOp::Previous => { |
405 | 0 | let (px, py, width, height) = self |
406 | 0 | .dispose_region |
407 | 0 | .expect("The first frame must not set dispose=Previous"); |
408 | 0 | let region_previous = previous.sub_image(px, py, width, height); |
409 | 0 | current |
410 | 0 | .copy_from(®ion_previous.to_image(), px, py) |
411 | 0 | .unwrap(); |
412 | 0 | } |
413 | | } |
414 | | |
415 | | // The allocations from now on are not going to persist, |
416 | | // and will be destroyed at the end of the scope. |
417 | | // Clone the limits so that any changes to them die with the allocations. |
418 | 0 | let mut limits = self.inner.limits.clone(); |
419 | | |
420 | | // Read next frame data. |
421 | 0 | let raw_frame_size = self.inner.reader.output_buffer_size().ok_or_else(|| { |
422 | 0 | ImageError::Limits(LimitError::from_kind(LimitErrorKind::InsufficientMemory)) |
423 | 0 | })?; |
424 | | |
425 | 0 | limits.reserve_usize(raw_frame_size)?; |
426 | 0 | let mut buffer = vec![0; raw_frame_size]; |
427 | | // TODO: add `png::Reader::change_limits()` and call it here |
428 | | // to also constrain the internal buffer allocations in the PNG crate |
429 | 0 | self.inner |
430 | 0 | .reader |
431 | 0 | .next_frame(&mut buffer) |
432 | 0 | .map_err(ImageError::from_png)?; |
433 | 0 | let info = self.inner.reader.info(); |
434 | | |
435 | | // Find out how to interpret the decoded frame. |
436 | | let (width, height, px, py, blend); |
437 | 0 | match info.frame_control() { |
438 | 0 | None => { |
439 | 0 | width = info.width; |
440 | 0 | height = info.height; |
441 | 0 | px = 0; |
442 | 0 | py = 0; |
443 | 0 | blend = BlendOp::Source; |
444 | 0 | } |
445 | 0 | Some(fc) => { |
446 | 0 | width = fc.width; |
447 | 0 | height = fc.height; |
448 | 0 | px = fc.x_offset; |
449 | 0 | py = fc.y_offset; |
450 | 0 | blend = fc.blend_op; |
451 | 0 | self.dispose = fc.dispose_op; |
452 | 0 | } |
453 | | } |
454 | | |
455 | 0 | self.dispose_region = Some((px, py, width, height)); |
456 | | |
457 | | // Turn the data into an rgba image proper. |
458 | 0 | limits.reserve_buffer(width, height, COLOR_TYPE)?; |
459 | 0 | let source = match self.inner.color_type { |
460 | | ColorType::L8 => { |
461 | 0 | let image = ImageBuffer::<Luma<_>, _>::from_raw(width, height, buffer).unwrap(); |
462 | 0 | DynamicImage::ImageLuma8(image).into_rgba8() |
463 | | } |
464 | | ColorType::La8 => { |
465 | 0 | let image = ImageBuffer::<LumaA<_>, _>::from_raw(width, height, buffer).unwrap(); |
466 | 0 | DynamicImage::ImageLumaA8(image).into_rgba8() |
467 | | } |
468 | | ColorType::Rgb8 => { |
469 | 0 | let image = ImageBuffer::<Rgb<_>, _>::from_raw(width, height, buffer).unwrap(); |
470 | 0 | DynamicImage::ImageRgb8(image).into_rgba8() |
471 | | } |
472 | 0 | ColorType::Rgba8 => ImageBuffer::<Rgba<_>, _>::from_raw(width, height, buffer).unwrap(), |
473 | | ColorType::L16 | ColorType::Rgb16 | ColorType::La16 | ColorType::Rgba16 => { |
474 | | // TODO: to enable remove restriction in `animatable_color_type` method. |
475 | 0 | unreachable!("16-bit apng not yet support") |
476 | | } |
477 | 0 | _ => unreachable!("Invalid png color"), |
478 | | }; |
479 | | // We've converted the raw frame to RGBA8 and disposed of the original allocation |
480 | 0 | limits.free_usize(raw_frame_size); |
481 | | |
482 | 0 | match blend { |
483 | 0 | BlendOp::Source => { |
484 | 0 | current |
485 | 0 | .copy_from(&source, px, py) |
486 | 0 | .expect("Invalid png image not detected in png"); |
487 | 0 | } |
488 | | BlendOp::Over => { |
489 | | // TODO: investigate speed, speed-ups, and bounds-checks. |
490 | 0 | for (x, y, p) in source.enumerate_pixels() { |
491 | 0 | current.get_pixel_mut(x + px, y + py).blend(p); |
492 | 0 | } |
493 | | } |
494 | | } |
495 | | |
496 | | // Ok, we can proceed with actually remaining images. |
497 | 0 | self.remaining = remaining; |
498 | | // Return composited output buffer. |
499 | | |
500 | 0 | Ok(Some(self.current.as_ref().unwrap())) |
501 | 0 | } |
502 | | |
503 | 0 | fn animatable_color_type(&self) -> Result<(), ImageError> { |
504 | 0 | match self.inner.color_type { |
505 | 0 | ColorType::L8 | ColorType::Rgb8 | ColorType::La8 | ColorType::Rgba8 => Ok(()), |
506 | | // TODO: do not handle multi-byte colors. Remember to implement it in `mix_next_frame`. |
507 | | ColorType::L16 | ColorType::Rgb16 | ColorType::La16 | ColorType::Rgba16 => { |
508 | 0 | Err(unsupported_color(self.inner.color_type.into())) |
509 | | } |
510 | 0 | _ => unreachable!("{:?} not a valid png color", self.inner.color_type), |
511 | | } |
512 | 0 | } |
513 | | } |
514 | | |
515 | | impl<'a, R: BufRead + Seek + 'a> AnimationDecoder<'a> for ApngDecoder<R> { |
516 | 0 | fn into_frames(self) -> Frames<'a> { |
517 | | struct FrameIterator<R: BufRead + Seek>(ApngDecoder<R>); |
518 | | |
519 | | impl<R: BufRead + Seek> Iterator for FrameIterator<R> { |
520 | | type Item = ImageResult<Frame>; |
521 | | |
522 | 0 | fn next(&mut self) -> Option<Self::Item> { |
523 | 0 | let image = match self.0.mix_next_frame() { |
524 | 0 | Ok(Some(image)) => image.clone(), |
525 | 0 | Ok(None) => return None, |
526 | 0 | Err(err) => return Some(Err(err)), |
527 | | }; |
528 | | |
529 | 0 | let info = self.0.inner.reader.info(); |
530 | 0 | let fc = info.frame_control().unwrap(); |
531 | | // PNG delays are rations in seconds. |
532 | 0 | let num = u32::from(fc.delay_num) * 1_000u32; |
533 | 0 | let denom = match fc.delay_den { |
534 | | // The standard dictates to replace by 100 when the denominator is 0. |
535 | 0 | 0 => 100, |
536 | 0 | d => u32::from(d), |
537 | | }; |
538 | 0 | let delay = Delay::from_ratio(Ratio::new(num, denom)); |
539 | 0 | Some(Ok(Frame::from_parts(image, 0, 0, delay))) |
540 | 0 | } |
541 | | } |
542 | | |
543 | 0 | Frames::new(Box::new(FrameIterator(self))) |
544 | 0 | } |
545 | | } |
546 | | |
547 | | /// PNG encoder |
548 | | pub struct PngEncoder<W: Write> { |
549 | | w: W, |
550 | | compression: CompressionType, |
551 | | filter: FilterType, |
552 | | icc_profile: Vec<u8>, |
553 | | exif_metadata: Vec<u8>, |
554 | | } |
555 | | |
556 | | /// DEFLATE compression level of a PNG encoder. The default setting is `Fast`. |
557 | | #[derive(Clone, Copy, Debug, Eq, PartialEq)] |
558 | | #[non_exhaustive] |
559 | | #[derive(Default)] |
560 | | pub enum CompressionType { |
561 | | /// No compression whatsoever |
562 | | Uncompressed, |
563 | | /// Fast, minimal compression |
564 | | #[default] |
565 | | Fast, |
566 | | /// Balance between speed and compression level |
567 | | Balanced, |
568 | | /// High compression level |
569 | | Best, |
570 | | /// Detailed compression level between 1 and 9 |
571 | | Level(u8), |
572 | | } |
573 | | |
574 | | /// Filter algorithms used to process image data to improve compression. |
575 | | /// |
576 | | /// The default filter is `Adaptive`. |
577 | | #[derive(Clone, Copy, Debug, Eq, PartialEq)] |
578 | | #[non_exhaustive] |
579 | | #[derive(Default)] |
580 | | pub enum FilterType { |
581 | | /// No processing done, best used for low bit depth grayscale or data with a |
582 | | /// low color count |
583 | | NoFilter, |
584 | | /// Filters based on previous pixel in the same scanline |
585 | | Sub, |
586 | | /// Filters based on the scanline above |
587 | | Up, |
588 | | /// Filters based on the average of left and right neighbor pixels |
589 | | Avg, |
590 | | /// Algorithm that takes into account the left, upper left, and above pixels |
591 | | Paeth, |
592 | | /// Uses a heuristic to select one of the preceding filters for each |
593 | | /// scanline rather than one filter for the entire image |
594 | | #[default] |
595 | | Adaptive, |
596 | | } |
597 | | |
598 | | impl<W: Write> PngEncoder<W> { |
599 | | /// Create a new encoder that writes its output to ```w``` |
600 | 0 | pub fn new(w: W) -> PngEncoder<W> { |
601 | 0 | PngEncoder { |
602 | 0 | w, |
603 | 0 | compression: CompressionType::default(), |
604 | 0 | filter: FilterType::default(), |
605 | 0 | icc_profile: Vec::new(), |
606 | 0 | exif_metadata: Vec::new(), |
607 | 0 | } |
608 | 0 | } Unexecuted instantiation: <image::codecs::png::PngEncoder<&mut alloc::vec::Vec<u8>>>::new Unexecuted instantiation: <image::codecs::png::PngEncoder<&mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>>::new |
609 | | |
610 | | /// Create a new encoder that writes its output to `w` with `CompressionType` `compression` and |
611 | | /// `FilterType` `filter`. |
612 | | /// |
613 | | /// It is best to view the options as a _hint_ to the implementation on the smallest or fastest |
614 | | /// option for encoding a particular image. That is, using options that map directly to a PNG |
615 | | /// image parameter will use this parameter where possible. But variants that have no direct |
616 | | /// mapping may be interpreted differently in minor versions. The exact output is expressly |
617 | | /// __not__ part of the SemVer stability guarantee. |
618 | | /// |
619 | | /// Note that it is not optimal to use a single filter type, so an adaptive |
620 | | /// filter type is selected as the default. The filter which best minimizes |
621 | | /// file size may change with the type of compression used. |
622 | 0 | pub fn new_with_quality( |
623 | 0 | w: W, |
624 | 0 | compression: CompressionType, |
625 | 0 | filter: FilterType, |
626 | 0 | ) -> PngEncoder<W> { |
627 | 0 | PngEncoder { |
628 | 0 | w, |
629 | 0 | compression, |
630 | 0 | filter, |
631 | 0 | icc_profile: Vec::new(), |
632 | 0 | exif_metadata: Vec::new(), |
633 | 0 | } |
634 | 0 | } |
635 | | |
636 | 0 | fn encode_inner( |
637 | 0 | self, |
638 | 0 | data: &[u8], |
639 | 0 | width: u32, |
640 | 0 | height: u32, |
641 | 0 | color: ExtendedColorType, |
642 | 0 | ) -> ImageResult<()> { |
643 | 0 | let (ct, bits) = match color { |
644 | 0 | ExtendedColorType::L8 => (png::ColorType::Grayscale, png::BitDepth::Eight), |
645 | 0 | ExtendedColorType::L16 => (png::ColorType::Grayscale, png::BitDepth::Sixteen), |
646 | 0 | ExtendedColorType::La8 => (png::ColorType::GrayscaleAlpha, png::BitDepth::Eight), |
647 | 0 | ExtendedColorType::La16 => (png::ColorType::GrayscaleAlpha, png::BitDepth::Sixteen), |
648 | 0 | ExtendedColorType::Rgb8 => (png::ColorType::Rgb, png::BitDepth::Eight), |
649 | 0 | ExtendedColorType::Rgb16 => (png::ColorType::Rgb, png::BitDepth::Sixteen), |
650 | 0 | ExtendedColorType::Rgba8 => (png::ColorType::Rgba, png::BitDepth::Eight), |
651 | 0 | ExtendedColorType::Rgba16 => (png::ColorType::Rgba, png::BitDepth::Sixteen), |
652 | | _ => { |
653 | 0 | return Err(ImageError::Unsupported( |
654 | 0 | UnsupportedError::from_format_and_kind( |
655 | 0 | ImageFormat::Png.into(), |
656 | 0 | UnsupportedErrorKind::Color(color), |
657 | 0 | ), |
658 | 0 | )) |
659 | | } |
660 | | }; |
661 | | |
662 | 0 | let comp = match self.compression { |
663 | 0 | CompressionType::Balanced => png::Compression::Balanced, |
664 | 0 | CompressionType::Best => png::Compression::High, |
665 | 0 | CompressionType::Fast => png::Compression::Fast, |
666 | 0 | CompressionType::Uncompressed => png::Compression::NoCompression, |
667 | 0 | CompressionType::Level(0) => png::Compression::NoCompression, |
668 | 0 | CompressionType::Level(_) => png::Compression::Fast, // whatever, will be overridden |
669 | | }; |
670 | | |
671 | 0 | let advanced_comp = match self.compression { |
672 | | // Do not set level 0 as a Zlib level to avoid Zlib backend variance. |
673 | | // For example, in miniz_oxide level 0 is very slow. |
674 | 0 | CompressionType::Level(n @ 1..) => Some(DeflateCompression::Level(n)), |
675 | 0 | _ => None, |
676 | | }; |
677 | | |
678 | 0 | let filter = match self.filter { |
679 | 0 | FilterType::NoFilter => png::Filter::NoFilter, |
680 | 0 | FilterType::Sub => png::Filter::Sub, |
681 | 0 | FilterType::Up => png::Filter::Up, |
682 | 0 | FilterType::Avg => png::Filter::Avg, |
683 | 0 | FilterType::Paeth => png::Filter::Paeth, |
684 | 0 | FilterType::Adaptive => png::Filter::Adaptive, |
685 | | }; |
686 | | |
687 | 0 | let mut info = png::Info::with_size(width, height); |
688 | | |
689 | 0 | if !self.icc_profile.is_empty() { |
690 | 0 | info.icc_profile = Some(Cow::Borrowed(&self.icc_profile)); |
691 | 0 | } |
692 | 0 | if !self.exif_metadata.is_empty() { |
693 | 0 | info.exif_metadata = Some(Cow::Borrowed(&self.exif_metadata)); |
694 | 0 | } |
695 | | |
696 | 0 | let mut encoder = |
697 | 0 | png::Encoder::with_info(self.w, info).map_err(|e| ImageError::IoError(e.into()))?; Unexecuted instantiation: <image::codecs::png::PngEncoder<&mut alloc::vec::Vec<u8>>>::encode_inner::{closure#0}Unexecuted instantiation: <image::codecs::png::PngEncoder<&mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>>::encode_inner::{closure#0} |
698 | | |
699 | 0 | encoder.set_color(ct); |
700 | 0 | encoder.set_depth(bits); |
701 | 0 | encoder.set_compression(comp); |
702 | 0 | if let Some(compression) = advanced_comp { |
703 | 0 | encoder.set_deflate_compression(compression); |
704 | 0 | } |
705 | 0 | encoder.set_filter(filter); |
706 | 0 | let mut writer = encoder |
707 | 0 | .write_header() |
708 | 0 | .map_err(|e| ImageError::IoError(e.into()))?; Unexecuted instantiation: <image::codecs::png::PngEncoder<&mut alloc::vec::Vec<u8>>>::encode_inner::{closure#1}Unexecuted instantiation: <image::codecs::png::PngEncoder<&mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>>::encode_inner::{closure#1} |
709 | 0 | writer |
710 | 0 | .write_image_data(data) |
711 | 0 | .map_err(|e| ImageError::IoError(e.into())) Unexecuted instantiation: <image::codecs::png::PngEncoder<&mut alloc::vec::Vec<u8>>>::encode_inner::{closure#2}Unexecuted instantiation: <image::codecs::png::PngEncoder<&mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>>::encode_inner::{closure#2} |
712 | 0 | } Unexecuted instantiation: <image::codecs::png::PngEncoder<&mut alloc::vec::Vec<u8>>>::encode_inner Unexecuted instantiation: <image::codecs::png::PngEncoder<&mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>>::encode_inner |
713 | | } |
714 | | |
715 | | impl<W: Write> ImageEncoder for PngEncoder<W> { |
716 | | /// Write a PNG image with the specified width, height, and color type. |
717 | | /// |
718 | | /// For color types with 16-bit per channel or larger, the contents of `buf` should be in |
719 | | /// native endian. `PngEncoder` will automatically convert to big endian as required by the |
720 | | /// underlying PNG format. |
721 | | #[track_caller] |
722 | 0 | fn write_image( |
723 | 0 | self, |
724 | 0 | buf: &[u8], |
725 | 0 | width: u32, |
726 | 0 | height: u32, |
727 | 0 | color_type: ExtendedColorType, |
728 | 0 | ) -> ImageResult<()> { |
729 | | use ExtendedColorType::*; |
730 | | |
731 | 0 | let expected_buffer_len = color_type.buffer_size(width, height); |
732 | 0 | assert_eq!( |
733 | | expected_buffer_len, |
734 | 0 | buf.len() as u64, |
735 | 0 | "Invalid buffer length: expected {expected_buffer_len} got {} for {width}x{height} image", |
736 | 0 | buf.len(), |
737 | | ); |
738 | | |
739 | | // PNG images are big endian. For 16 bit per channel and larger types, |
740 | | // the buffer may need to be reordered to big endian per the |
741 | | // contract of `write_image`. |
742 | | // TODO: assumes equal channel bit depth. |
743 | 0 | match color_type { |
744 | | L8 | La8 | Rgb8 | Rgba8 => { |
745 | | // No reodering necessary for u8 |
746 | 0 | self.encode_inner(buf, width, height, color_type) |
747 | | } |
748 | | L16 | La16 | Rgb16 | Rgba16 => { |
749 | | // Because the buffer is immutable and the PNG encoder does not |
750 | | // yet take Write/Read traits, create a temporary buffer for |
751 | | // big endian reordering. |
752 | | let mut reordered; |
753 | 0 | let buf = if cfg!(target_endian = "little") { |
754 | 0 | reordered = vec_try_with_capacity(buf.len())?; |
755 | 0 | reordered.extend(buf.chunks_exact(2).flat_map(|le| [le[1], le[0]])); Unexecuted instantiation: <image::codecs::png::PngEncoder<&mut alloc::vec::Vec<u8>> as image::io::encoder::ImageEncoder>::write_image::{closure#0}Unexecuted instantiation: <image::codecs::png::PngEncoder<&mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>> as image::io::encoder::ImageEncoder>::write_image::{closure#0} |
756 | 0 | &reordered |
757 | | } else { |
758 | 0 | buf |
759 | | }; |
760 | 0 | self.encode_inner(buf, width, height, color_type) |
761 | | } |
762 | 0 | _ => Err(ImageError::Unsupported( |
763 | 0 | UnsupportedError::from_format_and_kind( |
764 | 0 | ImageFormat::Png.into(), |
765 | 0 | UnsupportedErrorKind::Color(color_type), |
766 | 0 | ), |
767 | 0 | )), |
768 | | } |
769 | 0 | } Unexecuted instantiation: <image::codecs::png::PngEncoder<&mut alloc::vec::Vec<u8>> as image::io::encoder::ImageEncoder>::write_image Unexecuted instantiation: <image::codecs::png::PngEncoder<&mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>> as image::io::encoder::ImageEncoder>::write_image |
770 | | |
771 | 0 | fn set_icc_profile(&mut self, icc_profile: Vec<u8>) -> Result<(), UnsupportedError> { |
772 | 0 | self.icc_profile = icc_profile; |
773 | 0 | Ok(()) |
774 | 0 | } Unexecuted instantiation: <image::codecs::png::PngEncoder<_> as image::io::encoder::ImageEncoder>::set_icc_profile Unexecuted instantiation: <image::codecs::png::PngEncoder<&mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>> as image::io::encoder::ImageEncoder>::set_icc_profile |
775 | | |
776 | 0 | fn set_exif_metadata(&mut self, exif: Vec<u8>) -> Result<(), UnsupportedError> { |
777 | 0 | self.exif_metadata = exif; |
778 | 0 | Ok(()) |
779 | 0 | } Unexecuted instantiation: <image::codecs::png::PngEncoder<_> as image::io::encoder::ImageEncoder>::set_exif_metadata Unexecuted instantiation: <image::codecs::png::PngEncoder<&mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>> as image::io::encoder::ImageEncoder>::set_exif_metadata |
780 | | } |
781 | | |
782 | | impl ImageError { |
783 | 11.2k | fn from_png(err: png::DecodingError) -> ImageError { |
784 | | use png::DecodingError::*; |
785 | 11.2k | match err { |
786 | 8.32k | IoError(err) => ImageError::IoError(err), |
787 | | // The input image was not a valid PNG. |
788 | 2.87k | err @ Format(_) => { |
789 | 2.87k | ImageError::Decoding(DecodingError::new(ImageFormat::Png.into(), err)) |
790 | | } |
791 | | // Other is used when: |
792 | | // - The decoder is polled for more animation frames despite being done (or not being animated |
793 | | // in the first place). |
794 | | // - The output buffer does not have the required size. |
795 | 0 | err @ Parameter(_) => ImageError::Parameter(ParameterError::from_kind( |
796 | 0 | ParameterErrorKind::Generic(err.to_string()), |
797 | 0 | )), |
798 | | LimitsExceeded => { |
799 | 79 | ImageError::Limits(LimitError::from_kind(LimitErrorKind::InsufficientMemory)) |
800 | | } |
801 | | } |
802 | 11.2k | } |
803 | | } |
804 | | |
805 | | #[cfg(test)] |
806 | | mod tests { |
807 | | use super::*; |
808 | | use crate::io::free_functions::decoder_to_vec; |
809 | | use std::io::{BufReader, Cursor, Read}; |
810 | | |
811 | | #[test] |
812 | | fn ensure_no_decoder_off_by_one() { |
813 | | let dec = PngDecoder::new(BufReader::new( |
814 | | std::fs::File::open("tests/images/png/bugfixes/debug_triangle_corners_widescreen.png") |
815 | | .unwrap(), |
816 | | )) |
817 | | .expect("Unable to read PNG file (does it exist?)"); |
818 | | |
819 | | assert_eq![(2000, 1000), dec.dimensions()]; |
820 | | |
821 | | assert_eq![ |
822 | | ColorType::Rgb8, |
823 | | dec.color_type(), |
824 | | "Image MUST have the Rgb8 format" |
825 | | ]; |
826 | | |
827 | | let correct_bytes = decoder_to_vec(dec) |
828 | | .expect("Unable to read file") |
829 | | .bytes() |
830 | | .map(|x| x.expect("Unable to read byte")) |
831 | | .collect::<Vec<u8>>(); |
832 | | |
833 | | assert_eq![6_000_000, correct_bytes.len()]; |
834 | | } |
835 | | |
836 | | #[test] |
837 | | fn underlying_error() { |
838 | | use std::error::Error; |
839 | | |
840 | | let mut not_png = |
841 | | std::fs::read("tests/images/png/bugfixes/debug_triangle_corners_widescreen.png") |
842 | | .unwrap(); |
843 | | not_png[0] = 0; |
844 | | |
845 | | let error = PngDecoder::new(Cursor::new(¬_png)).err().unwrap(); |
846 | | let _ = error |
847 | | .source() |
848 | | .unwrap() |
849 | | .downcast_ref::<png::DecodingError>() |
850 | | .expect("Caused by a png error"); |
851 | | } |
852 | | |
853 | | #[test] |
854 | | fn encode_bad_color_type() { |
855 | | // regression test for issue #1663 |
856 | | let image = DynamicImage::new_rgb32f(1, 1); |
857 | | let mut target = Cursor::new(vec![]); |
858 | | let _ = image.write_to(&mut target, ImageFormat::Png); |
859 | | } |
860 | | } |