image/codecs/
png.rs

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