Skip to main content

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