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