Skip to main content

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