1use crate::text_metadata::{EncodableTextChunk, ITXtChunk, TEXtChunk, ZTXtChunk};
3use crate::{chunk, encoder};
4use io::Write;
5use std::{borrow::Cow, convert::TryFrom, fmt, io};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9#[repr(u8)]
10pub enum ColorType {
11 Grayscale = 0,
13 Rgb = 2,
15 Indexed = 3,
17 GrayscaleAlpha = 4,
19 Rgba = 6,
21}
22
23impl ColorType {
24 pub fn samples(self) -> usize {
26 self.samples_u8().into()
27 }
28
29 pub(crate) fn samples_u8(self) -> u8 {
30 use self::ColorType::*;
31 match self {
32 Grayscale | Indexed => 1,
33 Rgb => 3,
34 GrayscaleAlpha => 2,
35 Rgba => 4,
36 }
37 }
38
39 pub fn from_u8(n: u8) -> Option<ColorType> {
41 match n {
42 0 => Some(ColorType::Grayscale),
43 2 => Some(ColorType::Rgb),
44 3 => Some(ColorType::Indexed),
45 4 => Some(ColorType::GrayscaleAlpha),
46 6 => Some(ColorType::Rgba),
47 _ => None,
48 }
49 }
50
51 pub(crate) fn checked_raw_row_length(self, depth: BitDepth, width: u32) -> Option<usize> {
52 let bits = u64::from(width) * u64::from(self.samples_u8()) * u64::from(depth.into_u8());
54 TryFrom::try_from(1 + (bits + 7) / 8).ok()
55 }
56
57 pub(crate) fn raw_row_length_from_width(self, depth: BitDepth, width: u32) -> usize {
58 let samples = width as usize * self.samples();
59 1 + match depth {
60 BitDepth::Sixteen => samples * 2,
61 BitDepth::Eight => samples,
62 subbyte => {
63 let samples_per_byte = 8 / subbyte as usize;
64 let whole = samples / samples_per_byte;
65 let fract = usize::from(samples % samples_per_byte > 0);
66 whole + fract
67 }
68 }
69 }
70
71 pub(crate) fn is_combination_invalid(self, bit_depth: BitDepth) -> bool {
72 ((bit_depth == BitDepth::One || bit_depth == BitDepth::Two || bit_depth == BitDepth::Four)
75 && (self == ColorType::Rgb
76 || self == ColorType::GrayscaleAlpha
77 || self == ColorType::Rgba))
78 || (bit_depth == BitDepth::Sixteen && self == ColorType::Indexed)
79 }
80}
81
82#[derive(Debug, Clone, Copy, PartialEq, Eq)]
85#[repr(u8)]
86pub enum BitDepth {
87 One = 1,
88 Two = 2,
89 Four = 4,
90 Eight = 8,
91 Sixteen = 16,
92}
93
94#[derive(Debug, Clone, Copy)]
99#[repr(u8)]
100pub(crate) enum BytesPerPixel {
101 One = 1,
102 Two = 2,
103 Three = 3,
104 Four = 4,
105 Six = 6,
106 Eight = 8,
107}
108
109impl BitDepth {
110 pub fn from_u8(n: u8) -> Option<BitDepth> {
112 match n {
113 1 => Some(BitDepth::One),
114 2 => Some(BitDepth::Two),
115 4 => Some(BitDepth::Four),
116 8 => Some(BitDepth::Eight),
117 16 => Some(BitDepth::Sixteen),
118 _ => None,
119 }
120 }
121
122 pub(crate) fn into_u8(self) -> u8 {
123 self as u8
124 }
125}
126
127#[derive(Clone, Copy, Debug)]
129pub struct PixelDimensions {
130 pub xppu: u32,
132 pub yppu: u32,
134 pub unit: Unit,
136}
137
138#[derive(Debug, Clone, Copy, PartialEq, Eq)]
139#[repr(u8)]
140pub enum Unit {
142 Unspecified = 0,
143 Meter = 1,
144}
145
146impl Unit {
147 pub fn from_u8(n: u8) -> Option<Unit> {
149 match n {
150 0 => Some(Unit::Unspecified),
151 1 => Some(Unit::Meter),
152 _ => None,
153 }
154 }
155}
156
157#[derive(Debug, Clone, Copy, PartialEq, Eq)]
159#[repr(u8)]
160pub enum DisposeOp {
161 None = 0,
163 Background = 1,
165 Previous = 2,
167}
168
169impl DisposeOp {
170 pub fn from_u8(n: u8) -> Option<DisposeOp> {
172 match n {
173 0 => Some(DisposeOp::None),
174 1 => Some(DisposeOp::Background),
175 2 => Some(DisposeOp::Previous),
176 _ => None,
177 }
178 }
179}
180
181impl fmt::Display for DisposeOp {
182 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
183 let name = match *self {
184 DisposeOp::None => "DISPOSE_OP_NONE",
185 DisposeOp::Background => "DISPOSE_OP_BACKGROUND",
186 DisposeOp::Previous => "DISPOSE_OP_PREVIOUS",
187 };
188 write!(f, "{}", name)
189 }
190}
191
192#[derive(Debug, Clone, Copy, PartialEq, Eq)]
194#[repr(u8)]
195pub enum BlendOp {
196 Source = 0,
198 Over = 1,
200}
201
202impl BlendOp {
203 pub fn from_u8(n: u8) -> Option<BlendOp> {
205 match n {
206 0 => Some(BlendOp::Source),
207 1 => Some(BlendOp::Over),
208 _ => None,
209 }
210 }
211}
212
213impl fmt::Display for BlendOp {
214 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
215 let name = match *self {
216 BlendOp::Source => "BLEND_OP_SOURCE",
217 BlendOp::Over => "BLEND_OP_OVER",
218 };
219 write!(f, "{}", name)
220 }
221}
222
223#[derive(Clone, Copy, Debug)]
225pub struct FrameControl {
226 pub sequence_number: u32,
228 pub width: u32,
230 pub height: u32,
232 pub x_offset: u32,
234 pub y_offset: u32,
236 pub delay_num: u16,
238 pub delay_den: u16,
240 pub dispose_op: DisposeOp,
242 pub blend_op: BlendOp,
244}
245
246impl Default for FrameControl {
247 fn default() -> FrameControl {
248 FrameControl {
249 sequence_number: 0,
250 width: 0,
251 height: 0,
252 x_offset: 0,
253 y_offset: 0,
254 delay_num: 1,
255 delay_den: 30,
256 dispose_op: DisposeOp::None,
257 blend_op: BlendOp::Source,
258 }
259 }
260}
261
262impl FrameControl {
263 pub fn set_seq_num(&mut self, s: u32) {
264 self.sequence_number = s;
265 }
266
267 pub fn inc_seq_num(&mut self, i: u32) {
268 self.sequence_number += i;
269 }
270
271 pub fn encode<W: Write>(self, w: &mut W) -> encoder::Result<()> {
272 let mut data = [0u8; 26];
273 data[..4].copy_from_slice(&self.sequence_number.to_be_bytes());
274 data[4..8].copy_from_slice(&self.width.to_be_bytes());
275 data[8..12].copy_from_slice(&self.height.to_be_bytes());
276 data[12..16].copy_from_slice(&self.x_offset.to_be_bytes());
277 data[16..20].copy_from_slice(&self.y_offset.to_be_bytes());
278 data[20..22].copy_from_slice(&self.delay_num.to_be_bytes());
279 data[22..24].copy_from_slice(&self.delay_den.to_be_bytes());
280 data[24] = self.dispose_op as u8;
281 data[25] = self.blend_op as u8;
282
283 encoder::write_chunk(w, chunk::fcTL, &data)
284 }
285}
286
287#[derive(Clone, Copy, Debug)]
289pub struct AnimationControl {
290 pub num_frames: u32,
292 pub num_plays: u32,
294}
295
296impl AnimationControl {
297 pub fn encode<W: Write>(self, w: &mut W) -> encoder::Result<()> {
298 let mut data = [0; 8];
299 data[..4].copy_from_slice(&self.num_frames.to_be_bytes());
300 data[4..].copy_from_slice(&self.num_plays.to_be_bytes());
301 encoder::write_chunk(w, chunk::acTL, &data)
302 }
303}
304
305#[derive(Debug, Clone, Copy)]
307pub enum Compression {
308 Default,
310 Fast,
312 Best,
318 #[deprecated(
319 since = "0.17.6",
320 note = "use one of the other compression levels instead, such as 'fast'"
321 )]
322 Huffman,
323 #[deprecated(
324 since = "0.17.6",
325 note = "use one of the other compression levels instead, such as 'fast'"
326 )]
327 Rle,
328}
329
330impl Default for Compression {
331 fn default() -> Self {
332 Self::Default
333 }
334}
335
336#[derive(Clone, Copy, Debug, PartialEq, Eq)]
339pub struct ScaledFloat(u32);
340
341impl ScaledFloat {
342 const SCALING: f32 = 100_000.0;
343
344 pub fn in_range(value: f32) -> bool {
346 value >= 0.0 && (value * Self::SCALING).floor() <= u32::MAX as f32
347 }
348
349 #[allow(clippy::float_cmp)] pub fn exact(value: f32) -> bool {
352 let there = Self::forward(value);
353 let back = Self::reverse(there);
354 value == back
355 }
356
357 fn forward(value: f32) -> u32 {
358 (value.max(0.0) * Self::SCALING).floor() as u32
359 }
360
361 fn reverse(encoded: u32) -> f32 {
362 encoded as f32 / Self::SCALING
363 }
364
365 pub fn new(value: f32) -> Self {
368 Self(Self::forward(value))
369 }
370
371 pub fn from_scaled(val: u32) -> Self {
373 Self(val)
374 }
375
376 pub fn into_scaled(self) -> u32 {
378 self.0
379 }
380
381 pub fn into_value(self) -> f32 {
383 Self::reverse(self.0)
384 }
385
386 pub(crate) fn encode_gama<W: Write>(self, w: &mut W) -> encoder::Result<()> {
387 encoder::write_chunk(w, chunk::gAMA, &self.into_scaled().to_be_bytes())
388 }
389}
390
391#[derive(Clone, Copy, Debug, PartialEq, Eq)]
393pub struct SourceChromaticities {
394 pub white: (ScaledFloat, ScaledFloat),
395 pub red: (ScaledFloat, ScaledFloat),
396 pub green: (ScaledFloat, ScaledFloat),
397 pub blue: (ScaledFloat, ScaledFloat),
398}
399
400impl SourceChromaticities {
401 pub fn new(white: (f32, f32), red: (f32, f32), green: (f32, f32), blue: (f32, f32)) -> Self {
402 SourceChromaticities {
403 white: (ScaledFloat::new(white.0), ScaledFloat::new(white.1)),
404 red: (ScaledFloat::new(red.0), ScaledFloat::new(red.1)),
405 green: (ScaledFloat::new(green.0), ScaledFloat::new(green.1)),
406 blue: (ScaledFloat::new(blue.0), ScaledFloat::new(blue.1)),
407 }
408 }
409
410 #[rustfmt::skip]
411 pub fn to_be_bytes(self) -> [u8; 32] {
412 let white_x = self.white.0.into_scaled().to_be_bytes();
413 let white_y = self.white.1.into_scaled().to_be_bytes();
414 let red_x = self.red.0.into_scaled().to_be_bytes();
415 let red_y = self.red.1.into_scaled().to_be_bytes();
416 let green_x = self.green.0.into_scaled().to_be_bytes();
417 let green_y = self.green.1.into_scaled().to_be_bytes();
418 let blue_x = self.blue.0.into_scaled().to_be_bytes();
419 let blue_y = self.blue.1.into_scaled().to_be_bytes();
420 [
421 white_x[0], white_x[1], white_x[2], white_x[3],
422 white_y[0], white_y[1], white_y[2], white_y[3],
423 red_x[0], red_x[1], red_x[2], red_x[3],
424 red_y[0], red_y[1], red_y[2], red_y[3],
425 green_x[0], green_x[1], green_x[2], green_x[3],
426 green_y[0], green_y[1], green_y[2], green_y[3],
427 blue_x[0], blue_x[1], blue_x[2], blue_x[3],
428 blue_y[0], blue_y[1], blue_y[2], blue_y[3],
429 ]
430 }
431
432 pub fn encode<W: Write>(self, w: &mut W) -> encoder::Result<()> {
433 encoder::write_chunk(w, chunk::cHRM, &self.to_be_bytes())
434 }
435}
436
437#[repr(u8)]
441#[derive(Clone, Copy, Debug, PartialEq, Eq)]
442pub enum SrgbRenderingIntent {
443 Perceptual = 0,
445 RelativeColorimetric = 1,
447 Saturation = 2,
449 AbsoluteColorimetric = 3,
451}
452
453impl SrgbRenderingIntent {
454 pub(crate) fn into_raw(self) -> u8 {
455 self as u8
456 }
457
458 pub(crate) fn from_raw(raw: u8) -> Option<Self> {
459 match raw {
460 0 => Some(SrgbRenderingIntent::Perceptual),
461 1 => Some(SrgbRenderingIntent::RelativeColorimetric),
462 2 => Some(SrgbRenderingIntent::Saturation),
463 3 => Some(SrgbRenderingIntent::AbsoluteColorimetric),
464 _ => None,
465 }
466 }
467
468 pub fn encode<W: Write>(self, w: &mut W) -> encoder::Result<()> {
469 encoder::write_chunk(w, chunk::sRGB, &[self.into_raw()])
470 }
471}
472
473#[derive(Clone, Copy, Debug, PartialEq, Eq)]
479pub struct CodingIndependentCodePoints {
480 pub color_primaries: u8,
484
485 pub transfer_function: u8,
490
491 pub matrix_coefficients: u8,
499
500 pub is_video_full_range_image: bool,
510}
511
512#[derive(Clone, Copy, Debug, PartialEq, Eq)]
517pub struct MasteringDisplayColorVolume {
518 pub chromaticities: SourceChromaticities,
520
521 pub max_luminance: u32,
526
527 pub min_luminance: u32,
532}
533
534#[derive(Clone, Copy, Debug, PartialEq, Eq)]
538pub struct ContentLightLevelInfo {
539 pub max_content_light_level: u32,
548
549 pub max_frame_average_light_level: u32,
560}
561
562#[derive(Clone, Debug)]
564#[non_exhaustive]
565pub struct Info<'a> {
566 pub width: u32,
567 pub height: u32,
568 pub bit_depth: BitDepth,
569 pub color_type: ColorType,
571 pub interlaced: bool,
572 pub sbit: Option<Cow<'a, [u8]>>,
574 pub trns: Option<Cow<'a, [u8]>>,
576 pub pixel_dims: Option<PixelDimensions>,
577 pub palette: Option<Cow<'a, [u8]>>,
579 pub gama_chunk: Option<ScaledFloat>,
582 pub chrm_chunk: Option<SourceChromaticities>,
585 pub bkgd: Option<Cow<'a, [u8]>>,
587
588 pub frame_control: Option<FrameControl>,
589 pub animation_control: Option<AnimationControl>,
590 pub compression: Compression,
591 pub source_gamma: Option<ScaledFloat>,
594 pub source_chromaticities: Option<SourceChromaticities>,
597 pub srgb: Option<SrgbRenderingIntent>,
601 pub icc_profile: Option<Cow<'a, [u8]>>,
603 pub coding_independent_code_points: Option<CodingIndependentCodePoints>,
605 pub mastering_display_color_volume: Option<MasteringDisplayColorVolume>,
607 pub content_light_level: Option<ContentLightLevelInfo>,
609 pub exif_metadata: Option<Cow<'a, [u8]>>,
611 pub uncompressed_latin1_text: Vec<TEXtChunk>,
613 pub compressed_latin1_text: Vec<ZTXtChunk>,
615 pub utf8_text: Vec<ITXtChunk>,
617}
618
619impl Default for Info<'_> {
620 fn default() -> Info<'static> {
621 Info {
622 width: 0,
623 height: 0,
624 bit_depth: BitDepth::Eight,
625 color_type: ColorType::Grayscale,
626 interlaced: false,
627 palette: None,
628 sbit: None,
629 trns: None,
630 gama_chunk: None,
631 chrm_chunk: None,
632 bkgd: None,
633 pixel_dims: None,
634 frame_control: None,
635 animation_control: None,
636 compression: Compression::Fast,
639 source_gamma: None,
640 source_chromaticities: None,
641 srgb: None,
642 icc_profile: None,
643 coding_independent_code_points: None,
644 mastering_display_color_volume: None,
645 content_light_level: None,
646 exif_metadata: None,
647 uncompressed_latin1_text: Vec::new(),
648 compressed_latin1_text: Vec::new(),
649 utf8_text: Vec::new(),
650 }
651 }
652}
653
654impl Info<'_> {
655 pub fn with_size(width: u32, height: u32) -> Self {
657 Info {
658 width,
659 height,
660 ..Default::default()
661 }
662 }
663
664 pub fn size(&self) -> (u32, u32) {
666 (self.width, self.height)
667 }
668
669 pub fn is_animated(&self) -> bool {
671 self.frame_control.is_some() && self.animation_control.is_some()
672 }
673
674 pub fn animation_control(&self) -> Option<&AnimationControl> {
676 self.animation_control.as_ref()
677 }
678
679 pub fn frame_control(&self) -> Option<&FrameControl> {
681 self.frame_control.as_ref()
682 }
683
684 pub fn bits_per_pixel(&self) -> usize {
686 self.color_type.samples() * self.bit_depth as usize
687 }
688
689 pub fn bytes_per_pixel(&self) -> usize {
691 self.color_type.samples() * ((self.bit_depth as usize + 7) >> 3)
694 }
695
696 pub(crate) fn bpp_in_prediction(&self) -> BytesPerPixel {
704 BytesPerPixel::from_usize(self.bytes_per_pixel())
705 }
706
707 pub fn raw_bytes(&self) -> usize {
709 self.height as usize * self.raw_row_length()
710 }
711
712 pub fn raw_row_length(&self) -> usize {
714 self.raw_row_length_from_width(self.width)
715 }
716
717 pub(crate) fn checked_raw_row_length(&self) -> Option<usize> {
718 self.color_type
719 .checked_raw_row_length(self.bit_depth, self.width)
720 }
721
722 pub fn raw_row_length_from_width(&self, width: u32) -> usize {
724 self.color_type
725 .raw_row_length_from_width(self.bit_depth, width)
726 }
727
728 pub(crate) fn set_source_srgb(&mut self, rendering_intent: SrgbRenderingIntent) {
735 self.srgb = Some(rendering_intent);
736 self.icc_profile = None;
737 }
738
739 #[deprecated(note = "Use Encoder+Writer instead")]
744 pub fn encode<W: Write>(&self, mut w: W) -> encoder::Result<()> {
745 let mut data = [0; 13];
747 data[..4].copy_from_slice(&self.width.to_be_bytes());
748 data[4..8].copy_from_slice(&self.height.to_be_bytes());
749 data[8] = self.bit_depth as u8;
750 data[9] = self.color_type as u8;
751 data[12] = self.interlaced as u8;
752 encoder::write_chunk(&mut w, chunk::IHDR, &data)?;
753
754 if let Some(pd) = self.pixel_dims {
756 let mut phys_data = [0; 9];
757 phys_data[0..4].copy_from_slice(&pd.xppu.to_be_bytes());
758 phys_data[4..8].copy_from_slice(&pd.yppu.to_be_bytes());
759 match pd.unit {
760 Unit::Meter => phys_data[8] = 1,
761 Unit::Unspecified => phys_data[8] = 0,
762 }
763 encoder::write_chunk(&mut w, chunk::pHYs, &phys_data)?;
764 }
765
766 if let Some(srgb) = &self.srgb {
768 srgb.encode(&mut w)?;
769
770 let srgb_gamma = crate::srgb::substitute_gamma();
772 if Some(srgb_gamma) == self.source_gamma {
773 srgb_gamma.encode_gama(&mut w)?
774 }
775 let srgb_chromaticities = crate::srgb::substitute_chromaticities();
776 if Some(srgb_chromaticities) == self.source_chromaticities {
777 srgb_chromaticities.encode(&mut w)?;
778 }
779 } else {
780 if let Some(gma) = self.source_gamma {
781 gma.encode_gama(&mut w)?
782 }
783 if let Some(chrms) = self.source_chromaticities {
784 chrms.encode(&mut w)?;
785 }
786 if let Some(iccp) = &self.icc_profile {
787 encoder::write_iccp_chunk(&mut w, "_", iccp)?
788 }
789 }
790
791 if let Some(exif) = &self.exif_metadata {
792 encoder::write_chunk(&mut w, chunk::eXIf, exif)?;
793 }
794
795 if let Some(actl) = self.animation_control {
796 actl.encode(&mut w)?;
797 }
798
799 if let Some(p) = &self.palette {
802 encoder::write_chunk(&mut w, chunk::PLTE, p)?;
803 };
804
805 if let Some(t) = &self.trns {
806 encoder::write_chunk(&mut w, chunk::tRNS, t)?;
807 }
808
809 for text_chunk in &self.uncompressed_latin1_text {
810 text_chunk.encode(&mut w)?;
811 }
812
813 for text_chunk in &self.compressed_latin1_text {
814 text_chunk.encode(&mut w)?;
815 }
816
817 for text_chunk in &self.utf8_text {
818 text_chunk.encode(&mut w)?;
819 }
820
821 Ok(())
822 }
823}
824
825impl BytesPerPixel {
826 pub(crate) fn from_usize(bpp: usize) -> Self {
827 match bpp {
828 1 => BytesPerPixel::One,
829 2 => BytesPerPixel::Two,
830 3 => BytesPerPixel::Three,
831 4 => BytesPerPixel::Four,
832 6 => BytesPerPixel::Six, 8 => BytesPerPixel::Eight, _ => unreachable!("Not a possible byte rounded pixel width"),
835 }
836 }
837
838 pub(crate) fn into_usize(self) -> usize {
839 self as usize
840 }
841}
842
843bitflags::bitflags! {
844 #[doc = "
849 ```c
850 /// Discard the alpha channel
851 const STRIP_ALPHA = 0x0002; // read only
852 /// Expand 1; 2 and 4-bit samples to bytes
853 const PACKING = 0x0004; // read and write
854 /// Change order of packed pixels to LSB first
855 const PACKSWAP = 0x0008; // read and write
856 /// Invert monochrome images
857 const INVERT_MONO = 0x0020; // read and write
858 /// Normalize pixels to the sBIT depth
859 const SHIFT = 0x0040; // read and write
860 /// Flip RGB to BGR; RGBA to BGRA
861 const BGR = 0x0080; // read and write
862 /// Flip RGBA to ARGB or GA to AG
863 const SWAP_ALPHA = 0x0100; // read and write
864 /// Byte-swap 16-bit samples
865 const SWAP_ENDIAN = 0x0200; // read and write
866 /// Change alpha from opacity to transparency
867 const INVERT_ALPHA = 0x0400; // read and write
868 const STRIP_FILLER = 0x0800; // write only
869 const STRIP_FILLER_BEFORE = 0x0800; // write only
870 const STRIP_FILLER_AFTER = 0x1000; // write only
871 const GRAY_TO_RGB = 0x2000; // read only
872 const EXPAND_16 = 0x4000; // read only
873 /// Similar to STRIP_16 but in libpng considering gamma?
874 /// Not entirely sure the documentation says it is more
875 /// accurate but doesn't say precisely how.
876 const SCALE_16 = 0x8000; // read only
877 ```
878 "]
879 pub struct Transformations: u32 {
880 const IDENTITY = 0x00000; const STRIP_16 = 0x00001; const EXPAND = 0x00010; const ALPHA = 0x10000; }
891}
892
893impl Transformations {
894 pub fn normalize_to_color8() -> Transformations {
899 Transformations::EXPAND | Transformations::STRIP_16
900 }
901}
902
903impl Default for Transformations {
905 fn default() -> Transformations {
906 Transformations::IDENTITY
907 }
908}
909
910#[derive(Debug)]
911pub struct ParameterError {
912 inner: ParameterErrorKind,
913}
914
915#[derive(Debug)]
916pub(crate) enum ParameterErrorKind {
917 ImageBufferSize { expected: usize, actual: usize },
923 PolledAfterEndOfImage,
930 PolledAfterFatalError,
934}
935
936impl From<ParameterErrorKind> for ParameterError {
937 fn from(inner: ParameterErrorKind) -> Self {
938 ParameterError { inner }
939 }
940}
941
942impl fmt::Display for ParameterError {
943 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
944 use ParameterErrorKind::*;
945 match self.inner {
946 ImageBufferSize { expected, actual } => {
947 write!(fmt, "wrong data size, expected {} got {}", expected, actual)
948 }
949 PolledAfterEndOfImage => write!(fmt, "End of image has been reached"),
950 PolledAfterFatalError => {
951 write!(fmt, "A fatal decoding error has been encounted earlier")
952 }
953 }
954 }
955}