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