Oregami
Repositories/oxedyne/fe2o3

oxedyne/fe2o3/fe2o3_text/tests/annealer_corpus/image_png.rs

41.5 KiB, 1 run

created by r1870400018:11742, which is this file's identity for as long as the history lasts, whatever it is later renamed to

download · who wrote it · its history

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
7use core::num::NonZeroU32;
8use std::borrow::Cow;
9use std::io::{BufRead, Seek, Write};
10
11use png::{BlendOp, DeflateCompression, DisposeOp};
12
13use crate::animation::{Delay, Ratio};
14use crate::color::{ColorType, ExtendedColorType};
15use crate::error::{
16 DecodingError, ImageError, ImageResult, LimitError, LimitErrorKind, ParameterError,
17 ParameterErrorKind, UnsupportedError, UnsupportedErrorKind,
18};
19use crate::io::decoder::DecodedMetadataHint;
20use crate::io::{
21 DecodedAnimationAttributes, DecodedImageAttributes, DecoderPreparedImage, FormatAttributes,
22 SequenceControl,
23};
24use crate::math::Rect;
25use crate::metadata::LoopCount;
26use crate::utils::vec_try_with_capacity;
27use crate::{
28 DynamicImage, GenericImage, GenericImageView, ImageDecoder, ImageEncoder, ImageFormat,
29 ImageLayout, Limits, Luma, LumaA, Rgb, Rgba,
30};
31
32// http://www.w3.org/TR/PNG-Structure.html
33// The first eight bytes of a PNG file always contain the following (decimal) values:
34pub(crate) const PNG_SIGNATURE: [u8; 8] = [137, 80, 78, 71, 13, 10, 26, 10];
35const XMP_KEY: &str = "XML:com.adobe.xmp";
36const IPTC_KEYS: &[&str] = &["Raw profile type iptc", "Raw profile type 8bim"];
37
38/// PNG decoder
39pub struct PngDecoder<R: BufRead + Seek> {
40 decoder: Option<png::Decoder<R>>,
41 reader: Option<png::Reader<R>>,
42 color_type: ColorType,
43 limits: Limits,
44}
45
46impl<R: BufRead + Seek> PngDecoder<R> {
47 /// Creates a new decoder that decodes from the stream ```r```
48 pub fn new(r: R) -> PngDecoder<R> {
49 Self::with_limits(r, Limits::no_limits())
50 }
51
52 /// Creates a new decoder that decodes from the stream ```r``` with the given limits.
53 pub fn with_limits(r: R, limits: Limits) -> PngDecoder<R> {
54 let max_bytes = usize::try_from(limits.max_alloc.unwrap_or(u64::MAX)).unwrap_or(usize::MAX);
55 let mut decoder = png::Decoder::new_with_limits(r, png::Limits { bytes: max_bytes });
56 decoder.set_ignore_text_chunk(false);
57
58 PngDecoder {
59 decoder: Some(decoder),
60 // We'll replace this once we have a reader.
61 color_type: ColorType::L8,
62 reader: None,
63 limits,
64 }
65 }
66
67 fn ensure_reader_and_header(&mut self) -> ImageResult<&mut png::Reader<R>> {
68 if self.reader.is_some() {
69 // We do this for borrow-checking issues, do not borrow self outside the conditional
70 // branch. So the None/Err case here is not reachable.
71 return self.reader.as_mut().ok_or_else(|| unreachable!());
72 }
73
74 let Some(mut decoder) = self.decoder.take() else {
75 return Err(reader_finished_already());
76 };
77
78 self.limits.check_support(&crate::LimitSupport::default())?;
79
80 let info = decoder.read_header_info().map_err(ImageError::from_png)?;
81 self.limits.check_dimensions(info.width, info.height)?;
82
83 // By default the PNG decoder will scale 16 bpc to 8 bpc, so custom
84 // transformations must be set. EXPAND preserves the default behavior
85 // expanding bpc < 8 to 8 bpc.
86 decoder.set_transformations(png::Transformations::EXPAND);
87 let reader = decoder.read_info().map_err(ImageError::from_png)?;
88 let (color_type, bits) = reader.output_color_type();
89
90 let color_type = match (color_type, bits) {
91 (png::ColorType::Grayscale, png::BitDepth::Eight) => ColorType::L8,
92 (png::ColorType::Grayscale, png::BitDepth::Sixteen) => ColorType::L16,
93 (png::ColorType::GrayscaleAlpha, png::BitDepth::Eight) => ColorType::La8,
94 (png::ColorType::GrayscaleAlpha, png::BitDepth::Sixteen) => ColorType::La16,
95 (png::ColorType::Rgb, png::BitDepth::Eight) => ColorType::Rgb8,
96 (png::ColorType::Rgb, png::BitDepth::Sixteen) => ColorType::Rgb16,
97 (png::ColorType::Rgba, png::BitDepth::Eight) => ColorType::Rgba8,
98 (png::ColorType::Rgba, png::BitDepth::Sixteen) => ColorType::Rgba16,
99
100 (png::ColorType::Grayscale, png::BitDepth::One) => {
101 return Err(unsupported_color(ExtendedColorType::L1))
102 }
103 (png::ColorType::GrayscaleAlpha, png::BitDepth::One) => {
104 return Err(unsupported_color(ExtendedColorType::La1))
105 }
106 (png::ColorType::Rgb, png::BitDepth::One) => {
107 return Err(unsupported_color(ExtendedColorType::Rgb1))
108 }
109 (png::ColorType::Rgba, png::BitDepth::One) => {
110 return Err(unsupported_color(ExtendedColorType::Rgba1))
111 }
112
113 (png::ColorType::Grayscale, png::BitDepth::Two) => {
114 return Err(unsupported_color(ExtendedColorType::L2))
115 }
116 (png::ColorType::GrayscaleAlpha, png::BitDepth::Two) => {
117 return Err(unsupported_color(ExtendedColorType::La2))
118 }
119 (png::ColorType::Rgb, png::BitDepth::Two) => {
120 return Err(unsupported_color(ExtendedColorType::Rgb2))
121 }
122 (png::ColorType::Rgba, png::BitDepth::Two) => {
123 return Err(unsupported_color(ExtendedColorType::Rgba2))
124 }
125
126 (png::ColorType::Grayscale, png::BitDepth::Four) => {
127 return Err(unsupported_color(ExtendedColorType::L4))
128 }
129 (png::ColorType::GrayscaleAlpha, png::BitDepth::Four) => {
130 return Err(unsupported_color(ExtendedColorType::La4))
131 }
132 (png::ColorType::Rgb, png::BitDepth::Four) => {
133 return Err(unsupported_color(ExtendedColorType::Rgb4))
134 }
135 (png::ColorType::Rgba, png::BitDepth::Four) => {
136 return Err(unsupported_color(ExtendedColorType::Rgba4))
137 }
138
139 (png::ColorType::Indexed, bits) => {
140 return Err(unsupported_color(ExtendedColorType::Unknown(bits as u8)))
141 }
142 };
143
144 self.color_type = color_type;
145 Ok(self.reader.insert(reader))
146 }
147
148 /// Returns the gamma value of the image or None if no gamma value is indicated.
149 ///
150 /// If an sRGB chunk is present this method returns a gamma value of 0.45455 and ignores the
151 /// value in the gAMA chunk. This is the recommended behavior according to the PNG standard:
152 ///
153 /// > When the sRGB chunk is present, [...] decoders that recognize the sRGB chunk but are not
154 /// > capable of colour management are recommended to ignore the gAMA and cHRM chunks, and use
155 /// > the values given above as if they had appeared in gAMA and cHRM chunks.
156 pub fn gamma_value(&self) -> ImageResult<Option<f64>> {
157 let Some(reader) = &self.reader else {
158 return Err(decoding_not_yet_started());
159 };
160
161 Ok(reader
162 .info()
163 .source_gamma
164 .map(|x| f64::from(x.into_scaled()) / 100_000.0))
165 }
166
167 /// Turn this into an iterator over the animation frames.
168 ///
169 /// Reading the complete animation requires more memory than reading the data from the IDAT
170 /// frame–multiple frame buffers need to be reserved at the same time. We further do not
171 /// support compositing 16-bit colors. In any case this would be lossy as the interface of
172 /// animation decoders does not support 16-bit colors.
173 ///
174 /// If something is not supported or a limit is violated then the decoding step that requires
175 /// them will fail and an error will be returned instead of the frame. No further frames will
176 /// be returned.
177 pub fn apng(self) -> ImageResult<ApngDecoder<R>> {
178 ApngDecoder::read_sequence_data(self)
179 }
180
181 /// Returns if the image contains an animation.
182 ///
183 /// Note that the file itself decides if the default image is considered to be part of the
184 /// animation. When it is not the common interpretation is to use it as a thumbnail.
185 ///
186 /// If a non-animated image is converted into an `ApngDecoder` then its iterator is empty.
187 pub fn is_apng(&self) -> ImageResult<bool> {
188 let Some(reader) = &self.reader else {
189 return Err(ImageError::Parameter(ParameterError::from_kind(
190 ParameterErrorKind::FailedAlready,
191 )));
192 };
193
194 Ok(reader.info().animation_control.is_some())
195 }
196
197 fn color_type_info(info: &png::Info<'_>) -> ExtendedColorType {
198 match (info.color_type, info.bit_depth) {
199 (png::ColorType::Grayscale, png::BitDepth::One) => ExtendedColorType::L1,
200 (png::ColorType::Grayscale, png::BitDepth::Two) => ExtendedColorType::L2,
201 (png::ColorType::Grayscale, png::BitDepth::Four) => ExtendedColorType::L4,
202 (png::ColorType::Grayscale, png::BitDepth::Eight) => ExtendedColorType::L8,
203 (png::ColorType::Grayscale, png::BitDepth::Sixteen) => ExtendedColorType::L16,
204 (png::ColorType::GrayscaleAlpha, png::BitDepth::One) => ExtendedColorType::La1,
205 (png::ColorType::GrayscaleAlpha, png::BitDepth::Two) => ExtendedColorType::La2,
206 (png::ColorType::GrayscaleAlpha, png::BitDepth::Four) => ExtendedColorType::La4,
207 (png::ColorType::GrayscaleAlpha, png::BitDepth::Eight) => ExtendedColorType::La8,
208 (png::ColorType::GrayscaleAlpha, png::BitDepth::Sixteen) => ExtendedColorType::La16,
209 (png::ColorType::Rgb, png::BitDepth::One) => ExtendedColorType::Rgb1,
210 (png::ColorType::Rgb, png::BitDepth::Two) => ExtendedColorType::Rgb2,
211 (png::ColorType::Rgb, png::BitDepth::Four) => ExtendedColorType::Rgb4,
212 (png::ColorType::Rgb, png::BitDepth::Eight) => ExtendedColorType::Rgb8,
213 (png::ColorType::Rgb, png::BitDepth::Sixteen) => ExtendedColorType::Rgb16,
214 (png::ColorType::Rgba, png::BitDepth::One) => ExtendedColorType::Rgba1,
215 (png::ColorType::Rgba, png::BitDepth::Two) => ExtendedColorType::Rgba2,
216 (png::ColorType::Rgba, png::BitDepth::Four) => ExtendedColorType::Rgba4,
217 (png::ColorType::Rgba, png::BitDepth::Eight) => ExtendedColorType::Rgba8,
218 (png::ColorType::Rgba, png::BitDepth::Sixteen) => ExtendedColorType::Rgba16,
219 (png::ColorType::Indexed, png::BitDepth::One) => ExtendedColorType::Unknown(1),
220 (png::ColorType::Indexed, png::BitDepth::Two) => ExtendedColorType::Unknown(2),
221 (png::ColorType::Indexed, png::BitDepth::Four) => ExtendedColorType::Unknown(4),
222 (png::ColorType::Indexed, png::BitDepth::Eight) => ExtendedColorType::Unknown(8),
223 (png::ColorType::Indexed, png::BitDepth::Sixteen) => ExtendedColorType::Unknown(16),
224 }
225 }
226}
227
228fn attributes_from_info(info: &png::Info<'_>) -> DecodedImageAttributes {
229 let delay = info.frame_control().map(|fc| {
230 // PNG delays are rations in seconds.
231 let num = u32::from(fc.delay_num) * 1_000u32;
232 let denom = match fc.delay_den {
233 // The standard dictates to replace by 100 when the denominator is 0.
234 0 => 100,
235 d => u32::from(d),
236 };
237
238 Delay::from_ratio(Ratio::new(num, denom))
239 });
240
241 DecodedImageAttributes {
242 // We do not set x_offset and y_offset since the decoder performs composition according
243 // to Dispose and blend. For reading raw frames we'd pass the `fc.x_offset` here.
244 delay,
245 ..DecodedImageAttributes::default()
246 }
247}
248
249fn unsupported_color(ect: ExtendedColorType) -> ImageError {
250 ImageError::Unsupported(UnsupportedError::from_format_and_kind(
251 ImageFormat::Png.into(),
252 UnsupportedErrorKind::Color(ect),
253 ))
254}
255
256fn decoding_not_yet_started() -> ImageError {
257 ImageError::Parameter(ParameterError::from_kind(ParameterErrorKind::NoMoreData))
258}
259
260fn decoding_started_already() -> ImageError {
261 ImageError::Parameter(ParameterError::from_kind(ParameterErrorKind::NoMoreData))
262}
263
264fn reader_finished_already() -> ImageError {
265 ImageError::Parameter(ParameterError::from_kind(ParameterErrorKind::NoMoreData))
266}
267
268impl<R: BufRead + Seek> ImageDecoder for PngDecoder<R> {
269 fn prepare_image(&mut self) -> ImageResult<DecoderPreparedImage> {
270 let reader = self.ensure_reader_and_header()?;
271 let (width, height) = reader.info().size();
272 Ok(DecoderPreparedImage::new(width, height, self.color_type))
273 }
274
275 fn format_attributes(&self) -> FormatAttributes {
276 FormatAttributes {
277 // is any sort of iTXT chunk.
278 // FIXME: we do not collect these in advance.
279 xmp: DecodedMetadataHint::InHeader,
280 // is any sort of iTXT chunk.
281 // FIXME: we do not collect these in advance.
282 iptc: DecodedMetadataHint::InHeader,
283 // see iCCP chunk order.
284 icc: DecodedMetadataHint::InHeader,
285 // see eXIf chunk order.
286 exif: DecodedMetadataHint::InHeader,
287 ..FormatAttributes::default()
288 }
289 }
290
291 /// Only for [`ApngDecoder`].
292 fn animation_attributes(&mut self) -> Option<DecodedAnimationAttributes> {
293 None
294 }
295
296 fn icc_profile(&mut self) -> ImageResult<Option<Vec<u8>>> {
297 let reader = self.ensure_reader_and_header()?;
298 Ok(reader.info().icc_profile.as_ref().map(|x| x.to_vec()))
299 }
300
301 fn exif_metadata(&mut self) -> ImageResult<Option<Vec<u8>>> {
302 let reader = self.ensure_reader_and_header()?;
303 Ok(reader.info().exif_metadata.as_ref().map(|x| x.to_vec()))
304 }
305
306 fn xmp_metadata(&mut self) -> ImageResult<Option<Vec<u8>>> {
307 let reader = self.ensure_reader_and_header()?;
308
309 if let Some(mut itx_chunk) = reader
310 .info()
311 .utf8_text
312 .iter()
313 .find(|chunk| chunk.keyword.contains(XMP_KEY))
314 .cloned()
315 {
316 itx_chunk.decompress_text().map_err(ImageError::from_png)?;
317 return itx_chunk
318 .get_text()
319 .map(|text| Some(text.as_bytes().to_vec()))
320 .map_err(ImageError::from_png);
321 }
322
323 Ok(None)
324 }
325
326 fn iptc_metadata(&mut self) -> ImageResult<Option<Vec<u8>>> {
327 let reader = self.ensure_reader_and_header()?;
328
329 if let Some(mut text_chunk) = reader
330 .info()
331 .compressed_latin1_text
332 .iter()
333 .find(|chunk| IPTC_KEYS.iter().any(|key| chunk.keyword.contains(key)))
334 .cloned()
335 {
336 text_chunk.decompress_text().map_err(ImageError::from_png)?;
337 return text_chunk
338 .get_text()
339 .map(|text| Some(text.as_bytes().to_vec()))
340 .map_err(ImageError::from_png);
341 }
342
343 if let Some(text_chunk) = reader
344 .info()
345 .uncompressed_latin1_text
346 .iter()
347 .find(|chunk| IPTC_KEYS.iter().any(|key| chunk.keyword.contains(key)))
348 .cloned()
349 {
350 return Ok(Some(text_chunk.text.into_bytes()));
351 }
352 Ok(None)
353 }
354
355 fn read_image(&mut self, buf: &mut [u8]) -> ImageResult<DecodedImageAttributes> {
356 let layout = self.prepare_image()?;
357 assert_eq!(u64::try_from(buf.len()), Ok(layout.total_bytes()));
358
359 let reader = self.ensure_reader_and_header()?;
360 let original_color_type = Self::color_type_info(reader.info());
361 reader.next_frame(buf).map_err(ImageError::from_png)?;
362
363 // PNG images are big endian. For 16 bit per channel and larger types, the buffer may need
364 // to be reordered to native endianness per the contract of `read_image`. Assumes equal
365 // depth which is the only supported output from `png` with our options.
366 let bpc = layout.layout.color.bytes_per_pixel() / layout.layout.color.channel_count();
367
368 match bpc {
369 1 => (), // No reodering necessary for u8
370 2 => buf.as_chunks_mut::<2>().0.iter_mut().for_each(|c| {
371 *c = u16::from_be_bytes(*c).to_ne_bytes();
372 }),
373 _ => unreachable!(),
374 }
375
376 Ok(DecodedImageAttributes {
377 original_color_type: Some(original_color_type),
378 ..DecodedImageAttributes::default()
379 })
380 }
381
382 fn set_limits(&mut self, limits: Limits) -> ImageResult<()> {
383 limits.check_support(&crate::LimitSupport::default())?;
384
385 if let Some(decoder) = &mut self.decoder {
386 decoder.set_limits(png::Limits {
387 bytes: match limits.max_alloc {
388 None => usize::MAX,
389 Some(limit) => limit.try_into().unwrap_or(usize::MAX),
390 },
391 });
392
393 self.limits = limits;
394 Ok(())
395 } else {
396 Err(decoding_started_already())
397 }
398 }
399}
400
401/// An animated adapter of [`PngDecoder`].
402///
403/// See [`PngDecoder::apng`] for more information.
404///
405/// [`PngDecoder`]: struct.PngDecoder.html
406/// [`PngDecoder::apng`]: struct.PngDecoder.html#method.apng
407pub struct ApngDecoder<R: BufRead + Seek> {
408 inner: PngDecoder<R>,
409 /// The current output buffer.
410 current: Option<DynamicImage>,
411 /// The previous output buffer, used for dispose op previous.
412 previous: Option<DynamicImage>,
413 /// The dispose op of the current frame.
414 dispose: DisposeOp,
415 /// Buffer to put the frame data which is to be composed onto the current frame.
416 raw_frame_buffer: Vec<u8>,
417
418 /// The region to dispose of the previous frame.
419 dispose_region: Option<Rect>,
420 /// The number of image still expected to be able to load.
421 remaining: u32,
422 /// The next (first) image is the thumbnail.
423 has_thumbnail: bool,
424}
425
426impl<R: BufRead + Seek> ApngDecoder<R> {
427 fn read_sequence_data(mut inner: PngDecoder<R>) -> ImageResult<Self> {
428 let reader = inner.ensure_reader_and_header()?;
429 let remaining = match reader.info().animation_control() {
430 // The expected number of fcTL in the remaining image.
431 Some(actl) => actl.num_frames,
432 None => 0,
433 };
434
435 // If the IDAT has no fcTL then it is not part of the animation counted by
436 // num_frames. All following fdAT chunks must be preceded by an fcTL
437 let has_thumbnail = reader.info().frame_control.is_none();
438
439 Ok(ApngDecoder {
440 inner,
441 current: None,
442 previous: None,
443 raw_frame_buffer: vec![],
444 dispose: DisposeOp::Background,
445 dispose_region: None,
446 remaining,
447 has_thumbnail,
448 })
449 }
450
451 /// Decode one subframe and overlay it on the canvas.
452 fn mix_next_frame(
453 &mut self,
454 buf: &mut [u8],
455 ) -> Result<Option<DecodedImageAttributes>, ImageError> {
456 // Remove this image from remaining.
457 self.remaining = match self.remaining.checked_sub(1) {
458 None => return Ok(None),
459 Some(next) => next,
460 };
461
462 // Allocate the buffers, honoring the memory limits
463 let layout = self.inner.prepare_image()?;
464 let ImageLayout {
465 width,
466 height,
467 color,
468 } = layout.layout;
469
470 assert_eq!(u64::try_from(buf.len()), Ok(layout.total_bytes()));
471
472 // Shorten ourselves to 0 in case of error.
473 let remaining = self.remaining;
474 self.remaining = 0;
475
476 // Skip the thumbnail that is not part of the animation.
477 if self.has_thumbnail {
478 let reader = self.inner.ensure_reader_and_header()?;
479 reader.next_frame(buf).map_err(ImageError::from_png)?;
480 self.has_thumbnail = false;
481 }
482
483 {
484 let limits = &mut self.inner.limits;
485
486 if self.previous.is_none() {
487 limits.reserve_buffer(width, height, color)?;
488 self.previous = Some(DynamicImage::new(width, height, color));
489 }
490
491 if self.current.is_none() {
492 limits.reserve_buffer(width, height, color)?;
493 self.current = Some(DynamicImage::new(width, height, color));
494 }
495 }
496
497 self.animatable_color_type()?;
498
499 // We've initialized them earlier in this function
500 let previous = self.previous.as_mut().unwrap();
501 let current = self.current.as_mut().unwrap();
502
503 // Dispose of the previous frame.
504 match self.dispose {
505 DisposeOp::None => {
506 previous.clone_from(current);
507 }
508 DisposeOp::Background => {
509 previous.clone_from(current);
510 if let Some(rect) = self.dispose_region {
511 let mut region_current = current.sub_image(rect);
512
513 // FIXME: This is a workaround for the fact that `pixels_mut` is not implemented
514 let pixels: Vec<_> = region_current.pixels().collect();
515
516 for (x, y, _) in &pixels {
517 region_current.put_pixel(*x, *y, Rgba::from([0, 0, 0, 0]));
518 }
519 } else {
520 // The first frame is always a background frame.
521 current.as_mut_bytes().fill(0);
522 }
523 }
524 DisposeOp::Previous => {
525 let rect = self
526 .dispose_region
527 .expect("The first frame must not set dispose=Previous");
528 let region_previous = previous.sub_image(rect);
529 current
530 .copy_from(&region_previous.to_image(), rect.x, rect.y)
531 .unwrap();
532 }
533 }
534
535 // The allocations from now on are not going to persist,
536 // and will be destroyed at the end of the scope.
537 // Clone the limits so that any changes to them die with the allocations.
538 let mut limits = self.inner.limits.clone();
539 let reader = self.inner.ensure_reader_and_header()?;
540
541 // Read next frame data.
542 let raw_frame_size = reader.output_buffer_size().ok_or_else(|| {
543 ImageError::Limits(LimitError::from_kind(LimitErrorKind::InsufficientMemory))
544 })?;
545
546 // The frame size depends on frame control. If possible, we want to read it into the
547 // (temporary) output buffer that's been allocated for us anyways.
548 let buffer = if raw_frame_size <= buf.len() {
549 &mut buf[..raw_frame_size]
550 } else if raw_frame_size <= self.raw_frame_buffer.len() {
551 &mut self.raw_frame_buffer[..raw_frame_size]
552 } else {
553 limits.free_usize(self.raw_frame_buffer.len());
554 limits.reserve_usize(raw_frame_size)?;
555 self.raw_frame_buffer.resize(raw_frame_size, 0);
556 &mut self.raw_frame_buffer[..]
557 };
558
559 // TODO: add `png::Reader::change_limits()` and call it here
560 // to also constrain the internal buffer allocations in the PNG crate
561 reader.next_frame(buffer).map_err(ImageError::from_png)?;
562
563 // Find out how to interpret the decoded frame.
564 let info = reader.info();
565 let attributes = attributes_from_info(info);
566
567 let (dispose_region, blend);
568 match info.frame_control() {
569 None => {
570 dispose_region = Rect {
571 width: info.width,
572 height: info.height,
573 x: 0,
574 y: 0,
575 };
576
577 blend = BlendOp::Source;
578 }
579 Some(fc) => {
580 dispose_region = Rect {
581 width: fc.width,
582 height: fc.height,
583 x: fc.x_offset,
584 y: fc.y_offset,
585 };
586
587 blend = fc.blend_op;
588 self.dispose = fc.dispose_op;
589 }
590 }
591
592 self.dispose_region = Some(dispose_region);
593
594 match blend {
595 BlendOp::Source => {
596 copy_pixel_bytes(
597 current.as_mut_bytes(),
598 &layout.layout,
599 &buffer[..],
600 &dispose_region,
601 );
602 }
603 BlendOp::Over => {
604 // TODO: investigate speed, speed-ups, and bounds-checks.
605 blend_pixel_bytes(
606 current.as_mut_bytes(),
607 &layout.layout,
608 &buffer[..],
609 &dispose_region,
610 )
611 }
612 }
613
614 // Ok, we can proceed with actually remaining images.
615 self.remaining = remaining;
616
617 // Return composited output buffer.
618 buf.copy_from_slice(current.as_bytes());
619
620 Ok(Some(attributes))
621 }
622
623 fn animatable_color_type(&self) -> Result<(), ImageError> {
624 match self.inner.color_type {
625 ColorType::L8
626 | ColorType::Rgb8
627 | ColorType::La8
628 | ColorType::Rgba8
629 | ColorType::L16
630 | ColorType::Rgb16
631 | ColorType::La16
632 | ColorType::Rgba16 => Ok(()),
633 _ => {
634 debug_assert!(false, "{:?} not a valid png color", self.inner.color_type);
635 Err(unsupported_color(self.inner.color_type.into()))
636 }
637 }
638 }
639}
640
641impl<R: BufRead + Seek> ImageDecoder for ApngDecoder<R> {
642 fn format_attributes(&self) -> FormatAttributes {
643 FormatAttributes {
644 supports_animation: true,
645 ..self.inner.format_attributes()
646 }
647 }
648
649 fn animation_attributes(&mut self) -> Option<DecodedAnimationAttributes> {
650 let count = if let Ok(reader) = self.inner.ensure_reader_and_header() {
651 reader.info().animation_control()
652 } else {
653 return None;
654 };
655
656 let loop_count = match count {
657 None => LoopCount::Infinite,
658 Some(actl) if actl.num_plays == 0 => LoopCount::Infinite,
659 Some(actl) => LoopCount::Finite(
660 NonZeroU32::new(actl.num_plays).expect("num_plays should be non-zero"),
661 ),
662 };
663
664 Some(DecodedAnimationAttributes { loop_count })
665 }
666
667 fn prepare_image(&mut self) -> ImageResult<DecoderPreparedImage> {
668 self.inner.prepare_image()
669 }
670
671 fn read_image(&mut self, buf: &mut [u8]) -> ImageResult<DecodedImageAttributes> {
672 self.mix_next_frame(buf)?
673 .ok_or_else(reader_finished_already)
674 }
675
676 fn more_images(&self) -> SequenceControl {
677 if self.remaining > 0 {
678 SequenceControl::MaybeMore
679 } else {
680 SequenceControl::None
681 }
682 }
683}
684
685/// PNG encoder
686pub struct PngEncoder<W: Write> {
687 w: W,
688 compression: CompressionType,
689 filter: FilterType,
690 icc_profile: Vec<u8>,
691 exif_metadata: Vec<u8>,
692 xmp_metadata: Option<String>,
693}
694
695/// DEFLATE compression level of a PNG encoder. The default setting is `Fast`.
696#[derive(Clone, Copy, Debug, Eq, PartialEq)]
697#[non_exhaustive]
698#[derive(Default)]
699pub enum CompressionType {
700 /// No compression whatsoever
701 Uncompressed,
702 /// Fast, minimal compression
703 #[default]
704 Fast,
705 /// Balance between speed and compression level
706 Balanced,
707 /// High compression level
708 Best,
709 /// Detailed compression level between 1 and 9
710 Level(u8),
711}
712
713/// Filter algorithms used to process image data to improve compression.
714///
715/// The default filter is `Adaptive`.
716#[derive(Clone, Copy, Debug, Eq, PartialEq)]
717#[non_exhaustive]
718#[derive(Default)]
719pub enum FilterType {
720 /// No processing done, best used for low bit depth grayscale or data with a
721 /// low color count
722 NoFilter,
723 /// Filters based on previous pixel in the same scanline
724 Sub,
725 /// Filters based on the scanline above
726 Up,
727 /// Filters based on the average of left and right neighbor pixels
728 Avg,
729 /// Algorithm that takes into account the left, upper left, and above pixels
730 Paeth,
731 /// Uses a heuristic to select one of the preceding filters for each
732 /// scanline rather than one filter for the entire image
733 #[default]
734 Adaptive,
735}
736
737impl<W: Write> PngEncoder<W> {
738 /// Create a new encoder that writes its output to ```w```
739 pub fn new(w: W) -> PngEncoder<W> {
740 PngEncoder {
741 w,
742 compression: CompressionType::default(),
743 filter: FilterType::default(),
744 icc_profile: Vec::new(),
745 exif_metadata: Vec::new(),
746 xmp_metadata: None,
747 }
748 }
749
750 /// Create a new encoder that writes its output to `w` with `CompressionType` `compression` and
751 /// `FilterType` `filter`.
752 ///
753 /// It is best to view the options as a _hint_ to the implementation on the smallest or fastest
754 /// option for encoding a particular image. That is, using options that map directly to a PNG
755 /// image parameter will use this parameter where possible. But variants that have no direct
756 /// mapping may be interpreted differently in minor versions. The exact output is expressly
757 /// __not__ part of the SemVer stability guarantee.
758 ///
759 /// Note that it is not optimal to use a single filter type, so an adaptive
760 /// filter type is selected as the default. The filter which best minimizes
761 /// file size may change with the type of compression used.
762 pub fn new_with_quality(
763 w: W,
764 compression: CompressionType,
765 filter: FilterType,
766 ) -> PngEncoder<W> {
767 PngEncoder {
768 w,
769 compression,
770 filter,
771 icc_profile: Vec::new(),
772 exif_metadata: Vec::new(),
773 xmp_metadata: None,
774 }
775 }
776
777 fn encode_inner(
778 self,
779 data: &[u8],
780 width: u32,
781 height: u32,
782 color: ExtendedColorType,
783 ) -> ImageResult<()> {
784 let (ct, bits) = match color {
785 ExtendedColorType::L8 => (png::ColorType::Grayscale, png::BitDepth::Eight),
786 ExtendedColorType::L16 => (png::ColorType::Grayscale, png::BitDepth::Sixteen),
787 ExtendedColorType::La8 => (png::ColorType::GrayscaleAlpha, png::BitDepth::Eight),
788 ExtendedColorType::La16 => (png::ColorType::GrayscaleAlpha, png::BitDepth::Sixteen),
789 ExtendedColorType::Rgb8 => (png::ColorType::Rgb, png::BitDepth::Eight),
790 ExtendedColorType::Rgb16 => (png::ColorType::Rgb, png::BitDepth::Sixteen),
791 ExtendedColorType::Rgba8 => (png::ColorType::Rgba, png::BitDepth::Eight),
792 ExtendedColorType::Rgba16 => (png::ColorType::Rgba, png::BitDepth::Sixteen),
793 _ => {
794 return Err(ImageError::Unsupported(
795 UnsupportedError::from_format_and_kind(
796 ImageFormat::Png.into(),
797 UnsupportedErrorKind::Color(color),
798 ),
799 ))
800 }
801 };
802
803 let comp = match self.compression {
804 CompressionType::Balanced => png::Compression::Balanced,
805 CompressionType::Best => png::Compression::High,
806 CompressionType::Fast => png::Compression::Fast,
807 CompressionType::Uncompressed => png::Compression::NoCompression,
808 CompressionType::Level(0) => png::Compression::NoCompression,
809 CompressionType::Level(_) => png::Compression::Fast, // whatever, will be overridden
810 };
811
812 let advanced_comp = match self.compression {
813 // Do not set level 0 as a Zlib level to avoid Zlib backend variance.
814 // For example, in miniz_oxide level 0 is very slow.
815 CompressionType::Level(n @ 1..) => Some(DeflateCompression::Level(n)),
816 _ => None,
817 };
818
819 let filter = match self.filter {
820 FilterType::NoFilter => png::Filter::NoFilter,
821 FilterType::Sub => png::Filter::Sub,
822 FilterType::Up => png::Filter::Up,
823 FilterType::Avg => png::Filter::Avg,
824 FilterType::Paeth => png::Filter::Paeth,
825 FilterType::Adaptive => png::Filter::Adaptive,
826 };
827
828 let mut info = png::Info::with_size(width, height);
829
830 if !self.icc_profile.is_empty() {
831 info.icc_profile = Some(Cow::Borrowed(&self.icc_profile));
832 }
833 if !self.exif_metadata.is_empty() {
834 info.exif_metadata = Some(Cow::Borrowed(&self.exif_metadata));
835 }
836
837 let mut encoder =
838 png::Encoder::with_info(self.w, info).map_err(|e| ImageError::IoError(e.into()))?;
839
840 if let Some(xmp_text) = self.xmp_metadata {
841 encoder
842 .add_itxt_chunk(XMP_KEY.to_string(), xmp_text)
843 .map_err(|e| ImageError::IoError(e.into()))?;
844 }
845
846 encoder.set_color(ct);
847 encoder.set_depth(bits);
848 encoder.set_compression(comp);
849 if let Some(compression) = advanced_comp {
850 encoder.set_deflate_compression(compression);
851 }
852 encoder.set_filter(filter);
853 let mut writer = encoder
854 .write_header()
855 .map_err(|e| ImageError::IoError(e.into()))?;
856 writer
857 .write_image_data(data)
858 .map_err(|e| ImageError::IoError(e.into()))
859 }
860}
861
862impl<W: Write> ImageEncoder for PngEncoder<W> {
863 /// Write a PNG image with the specified width, height, and color type.
864 ///
865 /// For color types with 16-bit per channel or larger, the contents of `buf` should be in
866 /// native endian. `PngEncoder` will automatically convert to big endian as required by the
867 /// underlying PNG format.
868 #[track_caller]
869 fn write_image(
870 self,
871 buf: &[u8],
872 width: u32,
873 height: u32,
874 color_type: ExtendedColorType,
875 ) -> ImageResult<()> {
876 use ExtendedColorType::*;
877
878 let expected_buffer_len = color_type.buffer_size(width, height);
879 assert_eq!(
880 expected_buffer_len,
881 buf.len() as u64,
882 "Invalid buffer length: expected {expected_buffer_len} got {} for {width}x{height} image",
883 buf.len(),
884 );
885
886 // PNG images are big endian. For 16 bit per channel and larger types,
887 // the buffer may need to be reordered to big endian per the
888 // contract of `write_image`.
889 // TODO: assumes equal channel bit depth.
890 match color_type {
891 L8 | La8 | Rgb8 | Rgba8 => {
892 // No reodering necessary for u8
893 self.encode_inner(buf, width, height, color_type)
894 }
895 L16 | La16 | Rgb16 | Rgba16 => {
896 // Because the buffer is immutable and the PNG encoder does not
897 // yet take Write/Read traits, create a temporary buffer for
898 // big endian reordering.
899 let mut reordered;
900 let buf = if cfg!(target_endian = "little") {
901 reordered = vec_try_with_capacity(buf.len())?;
902 reordered.extend(buf.as_chunks::<2>().0.iter().flat_map(|le| [le[1], le[0]]));
903 &reordered
904 } else {
905 buf
906 };
907 self.encode_inner(buf, width, height, color_type)
908 }
909 _ => Err(ImageError::Unsupported(
910 UnsupportedError::from_format_and_kind(
911 ImageFormat::Png.into(),
912 UnsupportedErrorKind::Color(color_type),
913 ),
914 )),
915 }
916 }
917
918 fn set_icc_profile(&mut self, icc_profile: Vec<u8>) -> Result<(), UnsupportedError> {
919 self.icc_profile = icc_profile;
920 Ok(())
921 }
922
923 fn set_exif_metadata(&mut self, exif: Vec<u8>) -> Result<(), UnsupportedError> {
924 self.exif_metadata = exif;
925 Ok(())
926 }
927
928 fn set_xmp_metadata(&mut self, xmp: Vec<u8>) -> Result<(), UnsupportedError> {
929 self.xmp_metadata = Some(String::from_utf8(xmp).map_err(|_| {
930 UnsupportedError::from_format_and_kind(
931 ImageFormat::Png.into(),
932 UnsupportedErrorKind::GenericFeature("XMP metadata is not valid UTF-8".to_string()),
933 )
934 })?);
935 Ok(())
936 }
937
938 fn make_compatible_img(
939 &self,
940 _: crate::io::encoder::MethodSealedToImage,
941 img: &DynamicImage,
942 ) -> Option<DynamicImage> {
943 use ColorType::*;
944 match img.color() {
945 Rgb32F => Some(img.to_rgb16().into()),
946 Rgba32F => Some(img.to_rgba16().into()),
947 L8 | La8 | Rgb8 | Rgba8 | L16 | La16 | Rgb16 | Rgba16 => None,
948 }
949 }
950}
951
952impl ImageError {
953 fn from_png(err: png::DecodingError) -> ImageError {
954 use png::DecodingError::*;
955 match err {
956 IoError(err) => ImageError::IoError(err),
957 // The input image was not a valid PNG.
958 err @ Format(_) => {
959 ImageError::Decoding(DecodingError::new(ImageFormat::Png.into(), err))
960 }
961 // Other is used when:
962 // - The decoder is polled for more animation frames despite being done (or not being animated
963 // in the first place).
964 // - The output buffer does not have the required size.
965 err @ Parameter(_) => ImageError::Parameter(ParameterError::from_kind(
966 ParameterErrorKind::Generic(err.to_string()),
967 )),
968 LimitsExceeded => {
969 ImageError::Limits(LimitError::from_kind(LimitErrorKind::InsufficientMemory))
970 }
971 }
972 }
973}
974
975fn copy_pixel_bytes(bytes: &mut [u8], layout: &ImageLayout, from: &[u8], region: &Rect) {
976 let bpp = usize::from(layout.color.bytes_per_pixel());
977
978 let bytes_per_row = layout.width as usize * bpp;
979 let bytes_per_copy = region.width as usize * bpp;
980
981 let start = region.x as usize * bpp + region.y as usize * bytes_per_row;
982 let from = &from[..region.height as usize * bytes_per_copy];
983
984 for (target, src) in bytes[start..]
985 .chunks_exact_mut(bytes_per_row)
986 .zip(from.chunks_exact(bytes_per_copy))
987 {
988 target[..bytes_per_copy].copy_from_slice(src);
989 }
990}
991
992fn blend_pixel_bytes(bytes: &mut [u8], layout: &ImageLayout, from: &[u8], region: &Rect) {
993 fn inner<P: crate::Pixel>(bytes: &mut [u8], region: &[u8])
994 where
995 P::Subpixel: bytemuck::Pod,
996 {
997 let target = bytemuck::cast_slice_mut::<_, P::Subpixel>(bytes);
998 let source = bytemuck::cast_slice::<_, P::Subpixel>(region);
999
1000 for (target, source) in target
1001 .chunks_exact_mut(usize::from(P::CHANNEL_COUNT))
1002 .zip(source.chunks_exact(usize::from(P::CHANNEL_COUNT)))
1003 {
1004 P::from_slice_mut(target).blend(P::from_slice(source));
1005 }
1006 }
1007
1008 let row_transformer = match layout.color {
1009 ColorType::L8 => inner::<Luma<u8>>,
1010 ColorType::La8 => inner::<LumaA<u8>>,
1011 ColorType::Rgb8 => inner::<Rgb<u8>>,
1012 ColorType::Rgba8 => inner::<Rgba<u8>>,
1013 ColorType::L16 => inner::<Luma<u16>>,
1014 ColorType::La16 => inner::<LumaA<u16>>,
1015 ColorType::Rgb16 => inner::<Rgb<u16>>,
1016 ColorType::Rgba16 => inner::<Rgba<u16>>,
1017 ColorType::Rgb32F | ColorType::Rgba32F => unreachable!("No floating point formats in PNG"),
1018 };
1019
1020 let bpp = usize::from(layout.color.bytes_per_pixel());
1021
1022 let bytes_per_row = layout.width as usize * bpp;
1023 let bytes_per_copy = region.width as usize * bpp;
1024
1025 let start = region.x as usize * bpp + region.y as usize * bytes_per_row;
1026 let from = &from[..region.height as usize * bytes_per_copy];
1027
1028 for (target, src) in bytes[start..]
1029 .chunks_exact_mut(bytes_per_row)
1030 .zip(from.chunks_exact(bytes_per_copy))
1031 {
1032 row_transformer(&mut target[..bytes_per_copy], src);
1033 }
1034}
1035
1036#[cfg(test)]
1037mod tests {
1038 use super::*;
1039 use crate::io::free_functions::decoder_to_vec;
1040 use std::io::{BufReader, Cursor, Read};
1041
1042 #[test]
1043 fn ensure_no_decoder_off_by_one() {
1044 let mut dec = PngDecoder::new(BufReader::new(
1045 std::fs::File::open("tests/images/png/bugfixes/debug_triangle_corners_widescreen.png")
1046 .unwrap(),
1047 ));
1048
1049 let layout = dec
1050 .prepare_image()
1051 .expect("Unable to read PNG file (does it exist?)");
1052
1053 assert_eq![(2000, 1000), layout.layout.dimensions()];
1054
1055 assert_eq![
1056 ColorType::Rgb8,
1057 layout.layout.color,
1058 "Image MUST have the Rgb8 format"
1059 ];
1060
1061 let (data, _) = decoder_to_vec(&mut dec).expect("Unable to read file");
1062
1063 let correct_bytes = data
1064 .bytes()
1065 .map(|x| x.expect("Unable to read byte"))
1066 .collect::<Vec<u8>>();
1067
1068 assert_eq![6_000_000, correct_bytes.len()];
1069 }
1070
1071 #[test]
1072 fn underlying_error() {
1073 use std::error::Error;
1074
1075 let mut not_png =
1076 std::fs::read("tests/images/png/bugfixes/debug_triangle_corners_widescreen.png")
1077 .unwrap();
1078 not_png[0] = 0;
1079
1080 let mut decoder = PngDecoder::new(Cursor::new(&not_png));
1081 let error = decoder.prepare_image().err().unwrap();
1082
1083 let _ = error
1084 .source()
1085 .unwrap()
1086 .downcast_ref::<png::DecodingError>()
1087 .expect("Caused by a png error");
1088 }
1089
1090 #[test]
1091 fn encode_bad_color_type() {
1092 // regression test for issues #1663 and #2787
1093 let image = DynamicImage::new_rgb32f(1, 1);
1094 let mut target = Cursor::new(vec![]);
1095 assert!(image.write_to(&mut target, ImageFormat::Png).is_ok());
1096 }
1097
1098 #[test]
1099 fn roundtrip_xmp() {
1100 let img = [255u8, 0, 0, 0, 255, 0, 0, 0, 255];
1101 let xmp = b"<x:xmpmeta xmlns:x=\"adobe:ns:meta/\"><rdf:RDF></rdf:RDF></x:xmpmeta>".to_vec();
1102
1103 let mut encoded = Vec::new();
1104 {
1105 let mut encoder = PngEncoder::new(&mut encoded);
1106 encoder.set_xmp_metadata(xmp.clone()).unwrap();
1107 encoder
1108 .write_image(&img, 3, 1, ExtendedColorType::Rgb8)
1109 .expect("Could not encode image");
1110 }
1111
1112 let mut decoder = PngDecoder::new(Cursor::new(&encoded));
1113 let _ = decoder.prepare_image().unwrap();
1114 let decoded_xmp = decoder
1115 .xmp_metadata()
1116 .expect("Error decoding XMP")
1117 .expect("XMP is empty");
1118 assert_eq!(xmp, decoded_xmp);
1119 }
1120}