1use std::borrow::Cow;
9use std::io::{BufRead, Seek, Write};
10use std::num::NonZeroU32;
11
12use png::{BlendOp, DeflateCompression, DisposeOp};
13
14use crate::animation::{Delay, Frame, Frames, Ratio};
15use crate::color::{Blend, ColorType, ExtendedColorType};
16use crate::error::{
17 DecodingError, ImageError, ImageResult, LimitError, LimitErrorKind, ParameterError,
18 ParameterErrorKind, UnsupportedError, UnsupportedErrorKind,
19};
20use crate::metadata::LoopCount;
21use crate::utils::vec_try_with_capacity;
22use crate::{
23 AnimationDecoder, DynamicImage, GenericImage, GenericImageView, ImageBuffer, ImageDecoder,
24 ImageEncoder, ImageFormat, Limits, Luma, LumaA, Rgb, Rgba, RgbaImage,
25};
26
27pub(crate) const PNG_SIGNATURE: [u8; 8] = [137, 80, 78, 71, 13, 10, 26, 10];
30const XMP_KEY: &str = "XML:com.adobe.xmp";
31const IPTC_KEYS: &[&str] = &["Raw profile type iptc", "Raw profile type 8bim"];
32
33pub struct PngDecoder<R: BufRead + Seek> {
35 color_type: ColorType,
36 reader: png::Reader<R>,
37 limits: Limits,
38}
39
40impl<R: BufRead + Seek> PngDecoder<R> {
41 pub fn new(r: R) -> ImageResult<PngDecoder<R>> {
43 Self::with_limits(r, Limits::no_limits())
44 }
45
46 pub fn with_limits(r: R, limits: Limits) -> ImageResult<PngDecoder<R>> {
48 limits.check_support(&crate::LimitSupport::default())?;
49
50 let max_bytes = usize::try_from(limits.max_alloc.unwrap_or(u64::MAX)).unwrap_or(usize::MAX);
51 let mut decoder = png::Decoder::new_with_limits(r, png::Limits { bytes: max_bytes });
52 decoder.set_ignore_text_chunk(false);
53
54 let info = decoder.read_header_info().map_err(ImageError::from_png)?;
55 limits.check_dimensions(info.width, info.height)?;
56
57 decoder.set_transformations(png::Transformations::EXPAND);
61 let reader = decoder.read_info().map_err(ImageError::from_png)?;
62 let (color_type, bits) = reader.output_color_type();
63 let color_type = match (color_type, bits) {
64 (png::ColorType::Grayscale, png::BitDepth::Eight) => ColorType::L8,
65 (png::ColorType::Grayscale, png::BitDepth::Sixteen) => ColorType::L16,
66 (png::ColorType::GrayscaleAlpha, png::BitDepth::Eight) => ColorType::La8,
67 (png::ColorType::GrayscaleAlpha, png::BitDepth::Sixteen) => ColorType::La16,
68 (png::ColorType::Rgb, png::BitDepth::Eight) => ColorType::Rgb8,
69 (png::ColorType::Rgb, png::BitDepth::Sixteen) => ColorType::Rgb16,
70 (png::ColorType::Rgba, png::BitDepth::Eight) => ColorType::Rgba8,
71 (png::ColorType::Rgba, png::BitDepth::Sixteen) => ColorType::Rgba16,
72
73 (png::ColorType::Grayscale, png::BitDepth::One) => {
74 return Err(unsupported_color(ExtendedColorType::L1))
75 }
76 (png::ColorType::GrayscaleAlpha, png::BitDepth::One) => {
77 return Err(unsupported_color(ExtendedColorType::La1))
78 }
79 (png::ColorType::Rgb, png::BitDepth::One) => {
80 return Err(unsupported_color(ExtendedColorType::Rgb1))
81 }
82 (png::ColorType::Rgba, png::BitDepth::One) => {
83 return Err(unsupported_color(ExtendedColorType::Rgba1))
84 }
85
86 (png::ColorType::Grayscale, png::BitDepth::Two) => {
87 return Err(unsupported_color(ExtendedColorType::L2))
88 }
89 (png::ColorType::GrayscaleAlpha, png::BitDepth::Two) => {
90 return Err(unsupported_color(ExtendedColorType::La2))
91 }
92 (png::ColorType::Rgb, png::BitDepth::Two) => {
93 return Err(unsupported_color(ExtendedColorType::Rgb2))
94 }
95 (png::ColorType::Rgba, png::BitDepth::Two) => {
96 return Err(unsupported_color(ExtendedColorType::Rgba2))
97 }
98
99 (png::ColorType::Grayscale, png::BitDepth::Four) => {
100 return Err(unsupported_color(ExtendedColorType::L4))
101 }
102 (png::ColorType::GrayscaleAlpha, png::BitDepth::Four) => {
103 return Err(unsupported_color(ExtendedColorType::La4))
104 }
105 (png::ColorType::Rgb, png::BitDepth::Four) => {
106 return Err(unsupported_color(ExtendedColorType::Rgb4))
107 }
108 (png::ColorType::Rgba, png::BitDepth::Four) => {
109 return Err(unsupported_color(ExtendedColorType::Rgba4))
110 }
111
112 (png::ColorType::Indexed, bits) => {
113 return Err(unsupported_color(ExtendedColorType::Unknown(bits as u8)))
114 }
115 };
116
117 Ok(PngDecoder {
118 color_type,
119 reader,
120 limits,
121 })
122 }
123
124 pub fn gamma_value(&self) -> ImageResult<Option<f64>> {
133 Ok(self
134 .reader
135 .info()
136 .source_gamma
137 .map(|x| f64::from(x.into_scaled()) / 100_000.0))
138 }
139
140 pub fn apng(self) -> ImageResult<ApngDecoder<R>> {
151 Ok(ApngDecoder::new(self))
152 }
153
154 pub fn is_apng(&self) -> ImageResult<bool> {
161 Ok(self.reader.info().animation_control.is_some())
162 }
163}
164
165fn unsupported_color(ect: ExtendedColorType) -> ImageError {
166 ImageError::Unsupported(UnsupportedError::from_format_and_kind(
167 ImageFormat::Png.into(),
168 UnsupportedErrorKind::Color(ect),
169 ))
170}
171
172impl<R: BufRead + Seek> ImageDecoder for PngDecoder<R> {
173 fn dimensions(&self) -> (u32, u32) {
174 self.reader.info().size()
175 }
176
177 fn color_type(&self) -> ColorType {
178 self.color_type
179 }
180
181 fn icc_profile(&mut self) -> ImageResult<Option<Vec<u8>>> {
182 Ok(self.reader.info().icc_profile.as_ref().map(|x| x.to_vec()))
183 }
184
185 fn exif_metadata(&mut self) -> ImageResult<Option<Vec<u8>>> {
186 Ok(self
187 .reader
188 .info()
189 .exif_metadata
190 .as_ref()
191 .map(|x| x.to_vec()))
192 }
193
194 fn xmp_metadata(&mut self) -> ImageResult<Option<Vec<u8>>> {
195 if let Some(mut itx_chunk) = self
196 .reader
197 .info()
198 .utf8_text
199 .iter()
200 .find(|chunk| chunk.keyword.contains(XMP_KEY))
201 .cloned()
202 {
203 itx_chunk.decompress_text().map_err(ImageError::from_png)?;
204 return itx_chunk
205 .get_text()
206 .map(|text| Some(text.as_bytes().to_vec()))
207 .map_err(ImageError::from_png);
208 }
209 Ok(None)
210 }
211
212 fn iptc_metadata(&mut self) -> ImageResult<Option<Vec<u8>>> {
213 if let Some(mut text_chunk) = self
214 .reader
215 .info()
216 .compressed_latin1_text
217 .iter()
218 .find(|chunk| IPTC_KEYS.iter().any(|key| chunk.keyword.contains(key)))
219 .cloned()
220 {
221 text_chunk.decompress_text().map_err(ImageError::from_png)?;
222 return text_chunk
223 .get_text()
224 .map(|text| Some(text.as_bytes().to_vec()))
225 .map_err(ImageError::from_png);
226 }
227
228 if let Some(text_chunk) = self
229 .reader
230 .info()
231 .uncompressed_latin1_text
232 .iter()
233 .find(|chunk| IPTC_KEYS.iter().any(|key| chunk.keyword.contains(key)))
234 .cloned()
235 {
236 return Ok(Some(text_chunk.text.into_bytes()));
237 }
238 Ok(None)
239 }
240
241 fn read_image(mut self, buf: &mut [u8]) -> ImageResult<()> {
242 assert_eq!(u64::try_from(buf.len()), Ok(self.total_bytes()));
243 self.reader.next_frame(buf).map_err(ImageError::from_png)?;
244 let bpc = self.color_type().bytes_per_pixel() / self.color_type().channel_count();
249
250 match bpc {
251 1 => (), 2 => buf.as_chunks_mut::<2>().0.iter_mut().for_each(|c| {
253 *c = u16::from_be_bytes(*c).to_ne_bytes();
254 }),
255 _ => unreachable!(),
256 }
257 Ok(())
258 }
259
260 fn read_image_boxed(self: Box<Self>, buf: &mut [u8]) -> ImageResult<()> {
261 (*self).read_image(buf)
262 }
263
264 fn set_limits(&mut self, limits: Limits) -> ImageResult<()> {
265 limits.check_support(&crate::LimitSupport::default())?;
266 let info = self.reader.info();
267 limits.check_dimensions(info.width, info.height)?;
268 self.limits = limits;
269 Ok(())
272 }
273}
274
275pub struct ApngDecoder<R: BufRead + Seek> {
283 inner: PngDecoder<R>,
284 current: Option<RgbaImage>,
286 previous: Option<RgbaImage>,
288 dispose: DisposeOp,
290
291 dispose_region: Option<(u32, u32, u32, u32)>,
293 remaining: u32,
295 has_thumbnail: bool,
297}
298
299impl<R: BufRead + Seek> ApngDecoder<R> {
300 fn new(inner: PngDecoder<R>) -> Self {
301 let info = inner.reader.info();
302 let remaining = match info.animation_control() {
303 Some(actl) => actl.num_frames,
305 None => 0,
306 };
307 let has_thumbnail = info.frame_control.is_none();
310 ApngDecoder {
311 inner,
312 current: None,
313 previous: None,
314 dispose: DisposeOp::Background,
315 dispose_region: None,
316 remaining,
317 has_thumbnail,
318 }
319 }
320
321 fn mix_next_frame(&mut self) -> Result<Option<&RgbaImage>, ImageError> {
325 const COLOR_TYPE: ColorType = ColorType::Rgba8;
327
328 let (width, height) = self.inner.dimensions();
330 {
331 let limits = &mut self.inner.limits;
332 if self.previous.is_none() {
333 limits.reserve_buffer(width, height, COLOR_TYPE)?;
334 self.previous = Some(RgbaImage::new(width, height));
335 }
336
337 if self.current.is_none() {
338 limits.reserve_buffer(width, height, COLOR_TYPE)?;
339 self.current = Some(RgbaImage::new(width, height));
340 }
341 }
342
343 self.remaining = match self.remaining.checked_sub(1) {
345 None => return Ok(None),
346 Some(next) => next,
347 };
348
349 let remaining = self.remaining;
351 self.remaining = 0;
352
353 if self.has_thumbnail {
355 let mut limits = self.inner.limits.clone();
357
358 let buffer_size = self.inner.reader.output_buffer_size().ok_or_else(|| {
359 ImageError::Limits(LimitError::from_kind(LimitErrorKind::InsufficientMemory))
360 })?;
361
362 limits.reserve_usize(buffer_size)?;
363 let mut buffer = vec![0; buffer_size];
364 self.inner
367 .reader
368 .next_frame(&mut buffer)
369 .map_err(ImageError::from_png)?;
370 self.has_thumbnail = false;
371 }
372
373 self.animatable_color_type()?;
374
375 let previous = self.previous.as_mut().unwrap();
377 let current = self.current.as_mut().unwrap();
378
379 match self.dispose {
382 DisposeOp::None => {
383 previous.clone_from(current);
384 }
385 DisposeOp::Background => {
386 previous.clone_from(current);
387 if let Some((px, py, width, height)) = self.dispose_region {
388 let mut region_current = current.sub_image(px, py, width, height);
389
390 let pixels: Vec<_> = region_current.pixels().collect();
392
393 for (x, y, _) in &pixels {
394 region_current.put_pixel(*x, *y, Rgba::from([0, 0, 0, 0]));
395 }
396 } else {
397 current.pixels_mut().for_each(|pixel| {
399 *pixel = Rgba::from([0, 0, 0, 0]);
400 });
401 }
402 }
403 DisposeOp::Previous => {
404 let (px, py, width, height) = self
405 .dispose_region
406 .expect("The first frame must not set dispose=Previous");
407 let region_previous = previous.sub_image(px, py, width, height);
408 current
409 .copy_from(®ion_previous.to_image(), px, py)
410 .unwrap();
411 }
412 }
413
414 let mut limits = self.inner.limits.clone();
418
419 let raw_frame_size = self.inner.reader.output_buffer_size().ok_or_else(|| {
421 ImageError::Limits(LimitError::from_kind(LimitErrorKind::InsufficientMemory))
422 })?;
423
424 limits.reserve_usize(raw_frame_size)?;
425 let mut buffer = vec![0; raw_frame_size];
426 self.inner
429 .reader
430 .next_frame(&mut buffer)
431 .map_err(ImageError::from_png)?;
432 let info = self.inner.reader.info();
433
434 let (width, height, px, py, blend);
436 match info.frame_control() {
437 None => {
438 width = info.width;
439 height = info.height;
440 px = 0;
441 py = 0;
442 blend = BlendOp::Source;
443 }
444 Some(fc) => {
445 width = fc.width;
446 height = fc.height;
447 px = fc.x_offset;
448 py = fc.y_offset;
449 blend = fc.blend_op;
450 self.dispose = fc.dispose_op;
451 }
452 }
453
454 self.dispose_region = Some((px, py, width, height));
455
456 limits.reserve_buffer(width, height, COLOR_TYPE)?;
458 let source = match self.inner.color_type {
459 ColorType::L8 => {
460 let image = ImageBuffer::<Luma<_>, _>::from_raw(width, height, buffer).unwrap();
461 DynamicImage::ImageLuma8(image).into_rgba8()
462 }
463 ColorType::La8 => {
464 let image = ImageBuffer::<LumaA<_>, _>::from_raw(width, height, buffer).unwrap();
465 DynamicImage::ImageLumaA8(image).into_rgba8()
466 }
467 ColorType::Rgb8 => {
468 let image = ImageBuffer::<Rgb<_>, _>::from_raw(width, height, buffer).unwrap();
469 DynamicImage::ImageRgb8(image).into_rgba8()
470 }
471 ColorType::Rgba8 => ImageBuffer::<Rgba<_>, _>::from_raw(width, height, buffer).unwrap(),
472 ColorType::L16 | ColorType::Rgb16 | ColorType::La16 | ColorType::Rgba16 => {
473 unreachable!("16-bit apng not yet support")
475 }
476 _ => unreachable!("Invalid png color"),
477 };
478 limits.free_usize(raw_frame_size);
480
481 match blend {
482 BlendOp::Source => {
483 current
484 .copy_from(&source, px, py)
485 .expect("Invalid png image not detected in png");
486 }
487 BlendOp::Over => {
488 for (x, y, p) in source.enumerate_pixels() {
490 current.get_pixel_mut(x + px, y + py).blend(p);
491 }
492 }
493 }
494
495 self.remaining = remaining;
497 Ok(Some(self.current.as_ref().unwrap()))
500 }
501
502 fn animatable_color_type(&self) -> Result<(), ImageError> {
503 match self.inner.color_type {
504 ColorType::L8 | ColorType::Rgb8 | ColorType::La8 | ColorType::Rgba8 => Ok(()),
505 ColorType::L16 | ColorType::Rgb16 | ColorType::La16 | ColorType::Rgba16 => {
507 Err(unsupported_color(self.inner.color_type.into()))
508 }
509 _ => unreachable!("{:?} not a valid png color", self.inner.color_type),
510 }
511 }
512}
513
514impl<'a, R: BufRead + Seek + 'a> AnimationDecoder<'a> for ApngDecoder<R> {
515 fn loop_count(&self) -> LoopCount {
516 match self.inner.reader.info().animation_control() {
517 None => LoopCount::Infinite,
518 Some(actl) => match NonZeroU32::new(actl.num_plays) {
519 None => LoopCount::Infinite,
520 Some(n) => LoopCount::Finite(n),
521 },
522 }
523 }
524
525 fn into_frames(self) -> Frames<'a> {
526 struct FrameIterator<R: BufRead + Seek>(ApngDecoder<R>);
527
528 impl<R: BufRead + Seek> Iterator for FrameIterator<R> {
529 type Item = ImageResult<Frame>;
530
531 fn next(&mut self) -> Option<Self::Item> {
532 let image = match self.0.mix_next_frame() {
533 Ok(Some(image)) => image.clone(),
534 Ok(None) => return None,
535 Err(err) => return Some(Err(err)),
536 };
537
538 let info = self.0.inner.reader.info();
539 let fc = info.frame_control().unwrap();
540 let num = u32::from(fc.delay_num) * 1_000u32;
542 let denom = match fc.delay_den {
543 0 => 100,
545 d => u32::from(d),
546 };
547 let delay = Delay::from_ratio(Ratio::new(num, denom));
548 Some(Ok(Frame::from_parts(image, 0, 0, delay)))
549 }
550 }
551
552 Frames::new(Box::new(FrameIterator(self)))
553 }
554}
555
556pub struct PngEncoder<W: Write> {
558 w: W,
559 compression: CompressionType,
560 filter: FilterType,
561 icc_profile: Vec<u8>,
562 exif_metadata: Vec<u8>,
563}
564
565#[derive(Clone, Copy, Debug, Eq, PartialEq)]
567#[non_exhaustive]
568#[derive(Default)]
569pub enum CompressionType {
570 Default,
572 #[default]
574 Fast,
575 Best,
577 Uncompressed,
579 Level(u8),
581}
582
583#[derive(Clone, Copy, Debug, Eq, PartialEq)]
587#[non_exhaustive]
588#[derive(Default)]
589pub enum FilterType {
590 NoFilter,
593 Sub,
595 Up,
597 Avg,
599 Paeth,
601 #[default]
604 Adaptive,
605}
606
607impl<W: Write> PngEncoder<W> {
608 pub fn new(w: W) -> PngEncoder<W> {
610 PngEncoder {
611 w,
612 compression: CompressionType::default(),
613 filter: FilterType::default(),
614 icc_profile: Vec::new(),
615 exif_metadata: Vec::new(),
616 }
617 }
618
619 pub fn new_with_quality(
632 w: W,
633 compression: CompressionType,
634 filter: FilterType,
635 ) -> PngEncoder<W> {
636 PngEncoder {
637 w,
638 compression,
639 filter,
640 icc_profile: Vec::new(),
641 exif_metadata: Vec::new(),
642 }
643 }
644
645 fn encode_inner(
646 self,
647 data: &[u8],
648 width: u32,
649 height: u32,
650 color: ExtendedColorType,
651 ) -> ImageResult<()> {
652 let (ct, bits) = match color {
653 ExtendedColorType::L8 => (png::ColorType::Grayscale, png::BitDepth::Eight),
654 ExtendedColorType::L16 => (png::ColorType::Grayscale, png::BitDepth::Sixteen),
655 ExtendedColorType::La8 => (png::ColorType::GrayscaleAlpha, png::BitDepth::Eight),
656 ExtendedColorType::La16 => (png::ColorType::GrayscaleAlpha, png::BitDepth::Sixteen),
657 ExtendedColorType::Rgb8 => (png::ColorType::Rgb, png::BitDepth::Eight),
658 ExtendedColorType::Rgb16 => (png::ColorType::Rgb, png::BitDepth::Sixteen),
659 ExtendedColorType::Rgba8 => (png::ColorType::Rgba, png::BitDepth::Eight),
660 ExtendedColorType::Rgba16 => (png::ColorType::Rgba, png::BitDepth::Sixteen),
661 _ => {
662 return Err(ImageError::Unsupported(
663 UnsupportedError::from_format_and_kind(
664 ImageFormat::Png.into(),
665 UnsupportedErrorKind::Color(color),
666 ),
667 ))
668 }
669 };
670
671 let comp = match self.compression {
672 CompressionType::Default => png::Compression::Balanced,
673 CompressionType::Best => png::Compression::High,
674 CompressionType::Fast => png::Compression::Fast,
675 CompressionType::Uncompressed => png::Compression::NoCompression,
676 CompressionType::Level(0) => png::Compression::NoCompression,
677 CompressionType::Level(_) => png::Compression::Fast, };
679
680 let advanced_comp = match self.compression {
681 CompressionType::Level(n @ 1..) => Some(DeflateCompression::Level(n)),
684 _ => None,
685 };
686
687 let filter = match self.filter {
688 FilterType::NoFilter => png::Filter::NoFilter,
689 FilterType::Sub => png::Filter::Sub,
690 FilterType::Up => png::Filter::Up,
691 FilterType::Avg => png::Filter::Avg,
692 FilterType::Paeth => png::Filter::Paeth,
693 FilterType::Adaptive => png::Filter::Adaptive,
694 };
695
696 let mut info = png::Info::with_size(width, height);
697
698 if !self.icc_profile.is_empty() {
699 info.icc_profile = Some(Cow::Borrowed(&self.icc_profile));
700 }
701 if !self.exif_metadata.is_empty() {
702 info.exif_metadata = Some(Cow::Borrowed(&self.exif_metadata));
703 }
704
705 let mut encoder =
706 png::Encoder::with_info(self.w, info).map_err(|e| ImageError::IoError(e.into()))?;
707
708 encoder.set_color(ct);
709 encoder.set_depth(bits);
710 encoder.set_compression(comp);
711 if let Some(compression) = advanced_comp {
712 encoder.set_deflate_compression(compression);
713 }
714 encoder.set_filter(filter);
715 let mut writer = encoder
716 .write_header()
717 .map_err(|e| ImageError::IoError(e.into()))?;
718 writer
719 .write_image_data(data)
720 .map_err(|e| ImageError::IoError(e.into()))
721 }
722}
723
724impl<W: Write> ImageEncoder for PngEncoder<W> {
725 #[track_caller]
731 fn write_image(
732 self,
733 buf: &[u8],
734 width: u32,
735 height: u32,
736 color_type: ExtendedColorType,
737 ) -> ImageResult<()> {
738 use ExtendedColorType::*;
739
740 let expected_buffer_len = color_type.buffer_size(width, height);
741 assert_eq!(
742 expected_buffer_len,
743 buf.len() as u64,
744 "Invalid buffer length: expected {expected_buffer_len} got {} for {width}x{height} image",
745 buf.len(),
746 );
747
748 match color_type {
753 L8 | La8 | Rgb8 | Rgba8 => {
754 self.encode_inner(buf, width, height, color_type)
756 }
757 L16 | La16 | Rgb16 | Rgba16 => {
758 let mut reordered;
762 let buf = if cfg!(target_endian = "little") {
763 reordered = vec_try_with_capacity(buf.len())?;
764 reordered.extend(buf.as_chunks::<2>().0.iter().flat_map(|le| [le[1], le[0]]));
765 &reordered
766 } else {
767 buf
768 };
769 self.encode_inner(buf, width, height, color_type)
770 }
771 _ => Err(ImageError::Unsupported(
772 UnsupportedError::from_format_and_kind(
773 ImageFormat::Png.into(),
774 UnsupportedErrorKind::Color(color_type),
775 ),
776 )),
777 }
778 }
779
780 fn set_icc_profile(&mut self, icc_profile: Vec<u8>) -> Result<(), UnsupportedError> {
781 self.icc_profile = icc_profile;
782 Ok(())
783 }
784
785 fn set_exif_metadata(&mut self, exif: Vec<u8>) -> Result<(), UnsupportedError> {
786 self.exif_metadata = exif;
787 Ok(())
788 }
789}
790
791impl ImageError {
792 fn from_png(err: png::DecodingError) -> ImageError {
793 use png::DecodingError::*;
794 match err {
795 IoError(err) => ImageError::IoError(err),
796 err @ Format(_) => {
798 ImageError::Decoding(DecodingError::new(ImageFormat::Png.into(), err))
799 }
800 err @ Parameter(_) => ImageError::Parameter(ParameterError::from_kind(
805 ParameterErrorKind::Generic(err.to_string()),
806 )),
807 LimitsExceeded => {
808 ImageError::Limits(LimitError::from_kind(LimitErrorKind::InsufficientMemory))
809 }
810 }
811 }
812}
813
814#[cfg(test)]
815mod tests {
816 use super::*;
817 use crate::io::free_functions::decoder_to_vec;
818 use std::io::{BufReader, Cursor, Read};
819
820 #[test]
821 fn ensure_no_decoder_off_by_one() {
822 let dec = PngDecoder::new(BufReader::new(
823 std::fs::File::open("tests/images/png/bugfixes/debug_triangle_corners_widescreen.png")
824 .unwrap(),
825 ))
826 .expect("Unable to read PNG file (does it exist?)");
827
828 assert_eq![(2000, 1000), dec.dimensions()];
829
830 assert_eq![
831 ColorType::Rgb8,
832 dec.color_type(),
833 "Image MUST have the Rgb8 format"
834 ];
835
836 let correct_bytes = decoder_to_vec(dec)
837 .expect("Unable to read file")
838 .bytes()
839 .map(|x| x.expect("Unable to read byte"))
840 .collect::<Vec<u8>>();
841
842 assert_eq![6_000_000, correct_bytes.len()];
843 }
844
845 #[test]
846 fn underlying_error() {
847 use std::error::Error;
848
849 let mut not_png =
850 std::fs::read("tests/images/png/bugfixes/debug_triangle_corners_widescreen.png")
851 .unwrap();
852 not_png[0] = 0;
853
854 let error = PngDecoder::new(Cursor::new(¬_png)).err().unwrap();
855 let _ = error
856 .source()
857 .unwrap()
858 .downcast_ref::<png::DecodingError>()
859 .expect("Caused by a png error");
860 }
861
862 #[test]
863 fn encode_bad_color_type() {
864 let image = DynamicImage::new_rgb32f(1, 1);
866 let mut target = Cursor::new(vec![]);
867 let _ = image.write_to(&mut target, ImageFormat::Png);
868 }
869}