Coverage Report

Created: 2025-12-14 07:56

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