1 //! Encoding of PNM Images
2 use std::fmt;
3 use std::io;
4 
5 use std::io::Write;
6 
7 use super::AutoBreak;
8 use super::{ArbitraryHeader, ArbitraryTuplType, BitmapHeader, GraymapHeader, PixmapHeader};
9 use super::{HeaderRecord, PnmHeader, PNMSubtype, SampleEncoding};
10 use crate::color::{ColorType, ExtendedColorType};
11 use crate::error::{
12     ImageError, ImageResult, ParameterError, ParameterErrorKind, UnsupportedError,
13     UnsupportedErrorKind,
14 };
15 use crate::image::{ImageEncoder, ImageFormat};
16 
17 use byteorder::{BigEndian, WriteBytesExt};
18 
19 enum HeaderStrategy {
20     Dynamic,
21     Subtype(PNMSubtype),
22     Chosen(PnmHeader),
23 }
24 
25 #[derive(Clone, Copy)]
26 pub enum FlatSamples<'a> {
27     U8(&'a [u8]),
28     U16(&'a [u16]),
29 }
30 
31 /// Encodes images to any of the `pnm` image formats.
32 pub struct PnmEncoder<W: Write> {
33     writer: W,
34     header: HeaderStrategy,
35 }
36 
37 /// PNM Encoder
38 ///
39 /// An alias of [`PnmEncoder`].
40 ///
41 /// TODO: remove
42 ///
43 /// [`PnmEncoder`]: struct.PnmEncoder.html
44 #[allow(dead_code)]
45 #[deprecated(note = "Use `PnmEncoder` instead")]
46 pub type PNMEncoder<W> = PnmEncoder<W>;
47 
48 /// Encapsulate the checking system in the type system. Non of the fields are actually accessed
49 /// but requiring them forces us to validly construct the struct anyways.
50 struct CheckedImageBuffer<'a> {
51     _image: FlatSamples<'a>,
52     _width: u32,
53     _height: u32,
54     _color: ExtendedColorType,
55 }
56 
57 // Check the header against the buffer. Each struct produces the next after a check.
58 struct UncheckedHeader<'a> {
59     header: &'a PnmHeader,
60 }
61 
62 struct CheckedDimensions<'a> {
63     unchecked: UncheckedHeader<'a>,
64     width: u32,
65     height: u32,
66 }
67 
68 struct CheckedHeaderColor<'a> {
69     dimensions: CheckedDimensions<'a>,
70     color: ExtendedColorType,
71 }
72 
73 struct CheckedHeader<'a> {
74     color: CheckedHeaderColor<'a>,
75     encoding: TupleEncoding<'a>,
76     _image: CheckedImageBuffer<'a>,
77 }
78 
79 enum TupleEncoding<'a> {
80     PbmBits {
81         samples: FlatSamples<'a>,
82         width: u32,
83     },
84     Ascii {
85         samples: FlatSamples<'a>,
86     },
87     Bytes {
88         samples: FlatSamples<'a>,
89     },
90 }
91 
92 impl<W: Write> PnmEncoder<W> {
93     /// Create new PNMEncoder from the `writer`.
94     ///
95     /// The encoded images will have some `pnm` format. If more control over the image type is
96     /// required, use either one of `with_subtype` or `with_header`. For more information on the
97     /// behaviour, see `with_dynamic_header`.
new(writer: W) -> Self98     pub fn new(writer: W) -> Self {
99         PnmEncoder {
100             writer,
101             header: HeaderStrategy::Dynamic,
102         }
103     }
104 
105     /// Encode a specific pnm subtype image.
106     ///
107     /// The magic number and encoding type will be chosen as provided while the rest of the header
108     /// data will be generated dynamically. Trying to encode incompatible images (e.g. encoding an
109     /// RGB image as Graymap) will result in an error.
110     ///
111     /// This will overwrite the effect of earlier calls to `with_header` and `with_dynamic_header`.
with_subtype(self, subtype: PNMSubtype) -> Self112     pub fn with_subtype(self, subtype: PNMSubtype) -> Self {
113         PnmEncoder {
114             writer: self.writer,
115             header: HeaderStrategy::Subtype(subtype),
116         }
117     }
118 
119     /// Enforce the use of a chosen header.
120     ///
121     /// While this option gives the most control over the actual written data, the encoding process
122     /// will error in case the header data and image parameters do not agree. It is the users
123     /// obligation to ensure that the width and height are set accordingly, for example.
124     ///
125     /// Choose this option if you want a lossless decoding/encoding round trip.
126     ///
127     /// This will overwrite the effect of earlier calls to `with_subtype` and `with_dynamic_header`.
with_header(self, header: PnmHeader) -> Self128     pub fn with_header(self, header: PnmHeader) -> Self {
129         PnmEncoder {
130             writer: self.writer,
131             header: HeaderStrategy::Chosen(header),
132         }
133     }
134 
135     /// Create the header dynamically for each image.
136     ///
137     /// This is the default option upon creation of the encoder. With this, most images should be
138     /// encodable but the specific format chosen is out of the users control. The pnm subtype is
139     /// chosen arbitrarily by the library.
140     ///
141     /// This will overwrite the effect of earlier calls to `with_subtype` and `with_header`.
with_dynamic_header(self) -> Self142     pub fn with_dynamic_header(self) -> Self {
143         PnmEncoder {
144             writer: self.writer,
145             header: HeaderStrategy::Dynamic,
146         }
147     }
148 
149     /// Encode an image whose samples are represented as `u8`.
150     ///
151     /// Some `pnm` subtypes are incompatible with some color options, a chosen header most
152     /// certainly with any deviation from the original decoded image.
encode<'s, S>( &mut self, image: S, width: u32, height: u32, color: ColorType, ) -> ImageResult<()> where S: Into<FlatSamples<'s>>,153     pub fn encode<'s, S>(
154         &mut self,
155         image: S,
156         width: u32,
157         height: u32,
158         color: ColorType,
159     ) -> ImageResult<()>
160     where
161         S: Into<FlatSamples<'s>>,
162     {
163         let image = image.into();
164         match self.header {
165             HeaderStrategy::Dynamic => self.write_dynamic_header(image, width, height, color.into()),
166             HeaderStrategy::Subtype(subtype) => {
167                 self.write_subtyped_header(subtype, image, width, height, color.into())
168             }
169             HeaderStrategy::Chosen(ref header) => {
170                 Self::write_with_header(&mut self.writer, header, image, width, height, color.into())
171             }
172         }
173     }
174 
175     /// Choose any valid pnm format that the image can be expressed in and write its header.
176     ///
177     /// Returns how the body should be written if successful.
write_dynamic_header( &mut self, image: FlatSamples, width: u32, height: u32, color: ExtendedColorType, ) -> ImageResult<()>178     fn write_dynamic_header(
179         &mut self,
180         image: FlatSamples,
181         width: u32,
182         height: u32,
183         color: ExtendedColorType,
184     ) -> ImageResult<()> {
185         let depth = u32::from(color.channel_count());
186         let (maxval, tupltype) = match color {
187             ExtendedColorType::L1 => (1, ArbitraryTuplType::BlackAndWhite),
188             ExtendedColorType::L8 => (0xff, ArbitraryTuplType::Grayscale),
189             ExtendedColorType::L16 => (0xffff, ArbitraryTuplType::Grayscale),
190             ExtendedColorType::La1 => (1, ArbitraryTuplType::BlackAndWhiteAlpha),
191             ExtendedColorType::La8 => (0xff, ArbitraryTuplType::GrayscaleAlpha),
192             ExtendedColorType::La16 => (0xffff, ArbitraryTuplType::GrayscaleAlpha),
193             ExtendedColorType::Rgb8 => (0xff, ArbitraryTuplType::RGB),
194             ExtendedColorType::Rgb16 => (0xffff, ArbitraryTuplType::RGB),
195             ExtendedColorType::Rgba8 => (0xff, ArbitraryTuplType::RGBAlpha),
196             ExtendedColorType::Rgba16 => (0xffff, ArbitraryTuplType::RGBAlpha),
197             _ => {
198                 return Err(ImageError::Unsupported(
199                     UnsupportedError::from_format_and_kind(
200                         ImageFormat::Pnm.into(),
201                         UnsupportedErrorKind::Color(color),
202                     ),
203                 ))
204             }
205         };
206 
207         let header = PnmHeader {
208             decoded: HeaderRecord::Arbitrary(ArbitraryHeader {
209                 width,
210                 height,
211                 depth,
212                 maxval,
213                 tupltype: Some(tupltype),
214             }),
215             encoded: None,
216         };
217 
218         Self::write_with_header(&mut self.writer, &header, image, width, height, color)
219     }
220 
221     /// Try to encode the image with the chosen format, give its corresponding pixel encoding type.
write_subtyped_header( &mut self, subtype: PNMSubtype, image: FlatSamples, width: u32, height: u32, color: ExtendedColorType, ) -> ImageResult<()>222     fn write_subtyped_header(
223         &mut self,
224         subtype: PNMSubtype,
225         image: FlatSamples,
226         width: u32,
227         height: u32,
228         color: ExtendedColorType,
229     ) -> ImageResult<()> {
230         let header = match (subtype, color) {
231             (PNMSubtype::ArbitraryMap, color) => {
232                 return self.write_dynamic_header(image, width, height, color)
233             }
234             (PNMSubtype::Pixmap(encoding), ExtendedColorType::Rgb8) => PnmHeader {
235                 decoded: HeaderRecord::Pixmap(PixmapHeader {
236                     encoding,
237                     width,
238                     height,
239                     maxval: 255,
240                 }),
241                 encoded: None,
242             },
243             (PNMSubtype::Graymap(encoding), ExtendedColorType::L8) => PnmHeader {
244                 decoded: HeaderRecord::Graymap(GraymapHeader {
245                     encoding,
246                     width,
247                     height,
248                     maxwhite: 255,
249                 }),
250                 encoded: None,
251             },
252             (PNMSubtype::Bitmap(encoding), ExtendedColorType::L8)
253             | (PNMSubtype::Bitmap(encoding), ExtendedColorType::L1) => PnmHeader {
254                 decoded: HeaderRecord::Bitmap(BitmapHeader {
255                     encoding,
256                     width,
257                     height,
258                 }),
259                 encoded: None,
260             },
261             (_, _) => {
262                 return Err(ImageError::Parameter(ParameterError::from_kind(
263                     ParameterErrorKind::Generic(
264                         "Color type can not be represented in the chosen format".to_owned(),
265                     ),
266                 )));
267             }
268         };
269 
270         Self::write_with_header(&mut self.writer, &header, image, width, height, color)
271     }
272 
273     /// Try to encode the image with the chosen header, checking if values are correct.
274     ///
275     /// Returns how the body should be written if successful.
write_with_header( writer: &mut dyn Write, header: &PnmHeader, image: FlatSamples, width: u32, height: u32, color: ExtendedColorType, ) -> ImageResult<()>276     fn write_with_header(
277         writer: &mut dyn Write,
278         header: &PnmHeader,
279         image: FlatSamples,
280         width: u32,
281         height: u32,
282         color: ExtendedColorType,
283     ) -> ImageResult<()> {
284         let unchecked = UncheckedHeader { header };
285 
286         unchecked
287             .check_header_dimensions(width, height)?
288             .check_header_color(color)?
289             .check_sample_values(image)?
290             .write_header(writer)?
291             .write_image(writer)
292     }
293 }
294 
295 impl<W: Write> ImageEncoder for PnmEncoder<W> {
write_image( mut self, buf: &[u8], width: u32, height: u32, color_type: ColorType, ) -> ImageResult<()>296     fn write_image(
297         mut self,
298         buf: &[u8],
299         width: u32,
300         height: u32,
301         color_type: ColorType,
302     ) -> ImageResult<()> {
303         self.encode(buf, width, height, color_type)
304     }
305 }
306 
307 impl<'a> CheckedImageBuffer<'a> {
check( image: FlatSamples<'a>, width: u32, height: u32, color: ExtendedColorType, ) -> ImageResult<CheckedImageBuffer<'a>>308     fn check(
309         image: FlatSamples<'a>,
310         width: u32,
311         height: u32,
312         color: ExtendedColorType,
313     ) -> ImageResult<CheckedImageBuffer<'a>> {
314         let components = color.channel_count() as usize;
315         let uwidth = width as usize;
316         let uheight = height as usize;
317         let expected_len = components
318             .checked_mul(uwidth)
319             .and_then(|v| v.checked_mul(uheight));
320         if Some(image.len()) != expected_len {
321             // Image buffer does not correspond to size and colour.
322             return Err(ImageError::Parameter(ParameterError::from_kind(
323                 ParameterErrorKind::DimensionMismatch,
324             )));
325         }
326         Ok(CheckedImageBuffer {
327             _image: image,
328             _width: width,
329             _height: height,
330             _color: color,
331         })
332     }
333 }
334 
335 impl<'a> UncheckedHeader<'a> {
check_header_dimensions( self, width: u32, height: u32, ) -> ImageResult<CheckedDimensions<'a>>336     fn check_header_dimensions(
337         self,
338         width: u32,
339         height: u32,
340     ) -> ImageResult<CheckedDimensions<'a>> {
341         if self.header.width() != width || self.header.height() != height {
342             // Chosen header does not match Image dimensions.
343             return Err(ImageError::Parameter(ParameterError::from_kind(
344                 ParameterErrorKind::DimensionMismatch,
345             )));
346         }
347 
348         Ok(CheckedDimensions {
349             unchecked: self,
350             width,
351             height,
352         })
353     }
354 }
355 
356 impl<'a> CheckedDimensions<'a> {
357     // Check color compatibility with the header. This will only error when we are certain that
358     // the comination is bogus (e.g. combining Pixmap and Palette) but allows uncertain
359     // combinations (basically a ArbitraryTuplType::Custom with any color of fitting depth).
check_header_color(self, color: ExtendedColorType) -> ImageResult<CheckedHeaderColor<'a>>360     fn check_header_color(self, color: ExtendedColorType) -> ImageResult<CheckedHeaderColor<'a>> {
361         let components = u32::from(color.channel_count());
362 
363         match *self.unchecked.header {
364             PnmHeader {
365                 decoded: HeaderRecord::Bitmap(_),
366                 ..
367             } => match color {
368                 ExtendedColorType::L1 | ExtendedColorType::L8 | ExtendedColorType::L16 => (),
369                 _ => {
370                     return Err(ImageError::Parameter(ParameterError::from_kind(
371                         ParameterErrorKind::Generic(
372                             "PBM format only support luma color types".to_owned(),
373                         ),
374                     )))
375                 }
376             },
377             PnmHeader {
378                 decoded: HeaderRecord::Graymap(_),
379                 ..
380             } => match color {
381                 ExtendedColorType::L1 | ExtendedColorType::L8 | ExtendedColorType::L16 => (),
382                 _ => {
383                     return Err(ImageError::Parameter(ParameterError::from_kind(
384                         ParameterErrorKind::Generic(
385                             "PGM format only support luma color types".to_owned(),
386                         ),
387                     )))
388                 }
389             },
390             PnmHeader {
391                 decoded: HeaderRecord::Pixmap(_),
392                 ..
393             } => match color {
394                 ExtendedColorType::Rgb8 => (),
395                 _ => {
396                     return Err(ImageError::Parameter(ParameterError::from_kind(
397                         ParameterErrorKind::Generic(
398                             "PPM format only support ExtendedColorType::Rgb8".to_owned(),
399                         ),
400                     )))
401                 }
402             },
403             PnmHeader {
404                 decoded:
405                     HeaderRecord::Arbitrary(ArbitraryHeader {
406                         depth,
407                         ref tupltype,
408                         ..
409                     }),
410                 ..
411             } => match (tupltype, color) {
412                 (&Some(ArbitraryTuplType::BlackAndWhite), ExtendedColorType::L1) => (),
413                 (&Some(ArbitraryTuplType::BlackAndWhiteAlpha), ExtendedColorType::La8) => (),
414 
415                 (&Some(ArbitraryTuplType::Grayscale), ExtendedColorType::L1) => (),
416                 (&Some(ArbitraryTuplType::Grayscale), ExtendedColorType::L8) => (),
417                 (&Some(ArbitraryTuplType::Grayscale), ExtendedColorType::L16) => (),
418                 (&Some(ArbitraryTuplType::GrayscaleAlpha), ExtendedColorType::La8) => (),
419 
420                 (&Some(ArbitraryTuplType::RGB), ExtendedColorType::Rgb8) => (),
421                 (&Some(ArbitraryTuplType::RGBAlpha), ExtendedColorType::Rgba8) => (),
422 
423                 (&None, _) if depth == components => (),
424                 (&Some(ArbitraryTuplType::Custom(_)), _) if depth == components => (),
425                 _ if depth != components => {
426                     return Err(ImageError::Parameter(ParameterError::from_kind(
427                         ParameterErrorKind::Generic(format!(
428                             "Depth mismatch: header {} vs. color {}",
429                             depth, components
430                         )),
431                     )))
432                 }
433                 _ => {
434                     return Err(ImageError::Parameter(ParameterError::from_kind(
435                         ParameterErrorKind::Generic(
436                             "Invalid color type for selected PAM color type".to_owned(),
437                         ),
438                     )))
439                 }
440             },
441         }
442 
443         Ok(CheckedHeaderColor {
444             dimensions: self,
445             color,
446         })
447     }
448 }
449 
450 impl<'a> CheckedHeaderColor<'a> {
check_sample_values(self, image: FlatSamples<'a>) -> ImageResult<CheckedHeader<'a>>451     fn check_sample_values(self, image: FlatSamples<'a>) -> ImageResult<CheckedHeader<'a>> {
452         let header_maxval = match self.dimensions.unchecked.header.decoded {
453             HeaderRecord::Bitmap(_) => 1,
454             HeaderRecord::Graymap(GraymapHeader { maxwhite, .. }) => maxwhite,
455             HeaderRecord::Pixmap(PixmapHeader { maxval, .. }) => maxval,
456             HeaderRecord::Arbitrary(ArbitraryHeader { maxval, .. }) => maxval,
457         };
458 
459         // We trust the image color bit count to be correct at least.
460         let max_sample = match self.color {
461             ExtendedColorType::Unknown(n) if n <= 16 => (1 << n) - 1,
462             ExtendedColorType::L1 => 1,
463             ExtendedColorType::L8
464             | ExtendedColorType::La8
465             | ExtendedColorType::Rgb8
466             | ExtendedColorType::Rgba8
467             | ExtendedColorType::Bgr8
468             | ExtendedColorType::Bgra8
469                 => 0xff,
470             ExtendedColorType::L16
471             | ExtendedColorType::La16
472             | ExtendedColorType::Rgb16
473             | ExtendedColorType::Rgba16
474                 => 0xffff,
475             ExtendedColorType::__NonExhaustive(marker) => match marker._private {},
476             _ => {
477                 // Unsupported target color type.
478                 return Err(ImageError::Unsupported(
479                     UnsupportedError::from_format_and_kind(
480                         ImageFormat::Pnm.into(),
481                         UnsupportedErrorKind::Color(self.color),
482                     ),
483                 ));
484             }
485         };
486 
487         // Avoid the performance heavy check if possible, e.g. if the header has been chosen by us.
488         if header_maxval < max_sample && !image.all_smaller(header_maxval) {
489             // Sample value greater than allowed for chosen header.
490             return Err(ImageError::Unsupported(
491                 UnsupportedError::from_format_and_kind(
492                     ImageFormat::Pnm.into(),
493                     UnsupportedErrorKind::GenericFeature(
494                         "Sample value greater than allowed for chosen header".to_owned(),
495                     ),
496                 ),
497             ));
498         }
499 
500         let encoding = image.encoding_for(&self.dimensions.unchecked.header.decoded);
501 
502         let image = CheckedImageBuffer::check(
503             image,
504             self.dimensions.width,
505             self.dimensions.height,
506             self.color,
507         )?;
508 
509         Ok(CheckedHeader {
510             color: self,
511             encoding,
512             _image: image,
513         })
514     }
515 }
516 
517 impl<'a> CheckedHeader<'a> {
write_header(self, writer: &mut dyn Write) -> ImageResult<TupleEncoding<'a>>518     fn write_header(self, writer: &mut dyn Write) -> ImageResult<TupleEncoding<'a>> {
519         self.header().write(writer)?;
520         Ok(self.encoding)
521     }
522 
header(&self) -> &PnmHeader523     fn header(&self) -> &PnmHeader {
524         self.color.dimensions.unchecked.header
525     }
526 }
527 
528 struct SampleWriter<'a>(&'a mut dyn Write);
529 
530 impl<'a> SampleWriter<'a> {
write_samples_ascii<V>(self, samples: V) -> io::Result<()> where V: Iterator, V::Item: fmt::Display,531     fn write_samples_ascii<V>(self, samples: V) -> io::Result<()>
532     where
533         V: Iterator,
534         V::Item: fmt::Display,
535     {
536         let mut auto_break_writer = AutoBreak::new(self.0, 70);
537         for value in samples {
538             write!(auto_break_writer, "{} ", value)?;
539         }
540         auto_break_writer.flush()
541     }
542 
write_pbm_bits<V>(self, samples: &[V], width: u32) -> io::Result<()> where V: Default + Eq + Copy,543     fn write_pbm_bits<V>(self, samples: &[V], width: u32) -> io::Result<()>
544     /* Default gives 0 for all primitives. TODO: replace this with `Zeroable` once it hits stable */
545     where
546         V: Default + Eq + Copy,
547     {
548         // The length of an encoded scanline
549         let line_width = (width - 1) / 8 + 1;
550 
551         // We'll be writing single bytes, so buffer
552         let mut line_buffer = Vec::with_capacity(line_width as usize);
553 
554         for line in samples.chunks(width as usize) {
555             for byte_bits in line.chunks(8) {
556                 let mut byte = 0u8;
557                 for i in 0..8 {
558                     // Black pixels are encoded as 1s
559                     if let Some(&v) = byte_bits.get(i) {
560                         if v == V::default() {
561                             byte |= 1u8 << (7 - i)
562                         }
563                     }
564                 }
565                 line_buffer.push(byte)
566             }
567             self.0.write_all(line_buffer.as_slice())?;
568             line_buffer.clear();
569         }
570 
571         self.0.flush()
572     }
573 }
574 
575 impl<'a> FlatSamples<'a> {
len(&self) -> usize576     fn len(&self) -> usize {
577         match *self {
578             FlatSamples::U8(arr) => arr.len(),
579             FlatSamples::U16(arr) => arr.len(),
580         }
581     }
582 
all_smaller(&self, max_val: u32) -> bool583     fn all_smaller(&self, max_val: u32) -> bool {
584         match *self {
585             FlatSamples::U8(arr) => arr.iter().any(|&val| u32::from(val) > max_val),
586             FlatSamples::U16(arr) => arr.iter().any(|&val| u32::from(val) > max_val),
587         }
588     }
589 
encoding_for(&self, header: &HeaderRecord) -> TupleEncoding<'a>590     fn encoding_for(&self, header: &HeaderRecord) -> TupleEncoding<'a> {
591         match *header {
592             HeaderRecord::Bitmap(BitmapHeader {
593                 encoding: SampleEncoding::Binary,
594                 width,
595                 ..
596             }) => TupleEncoding::PbmBits {
597                 samples: *self,
598                 width,
599             },
600 
601             HeaderRecord::Bitmap(BitmapHeader {
602                 encoding: SampleEncoding::Ascii,
603                 ..
604             }) => TupleEncoding::Ascii { samples: *self },
605 
606             HeaderRecord::Arbitrary(_) => TupleEncoding::Bytes { samples: *self },
607 
608             HeaderRecord::Graymap(GraymapHeader {
609                 encoding: SampleEncoding::Ascii,
610                 ..
611             })
612             | HeaderRecord::Pixmap(PixmapHeader {
613                 encoding: SampleEncoding::Ascii,
614                 ..
615             }) => TupleEncoding::Ascii { samples: *self },
616 
617             HeaderRecord::Graymap(GraymapHeader {
618                 encoding: SampleEncoding::Binary,
619                 ..
620             })
621             | HeaderRecord::Pixmap(PixmapHeader {
622                 encoding: SampleEncoding::Binary,
623                 ..
624             }) => TupleEncoding::Bytes { samples: *self },
625         }
626     }
627 }
628 
629 impl<'a> From<&'a [u8]> for FlatSamples<'a> {
from(samples: &'a [u8]) -> Self630     fn from(samples: &'a [u8]) -> Self {
631         FlatSamples::U8(samples)
632     }
633 }
634 
635 impl<'a> From<&'a [u16]> for FlatSamples<'a> {
from(samples: &'a [u16]) -> Self636     fn from(samples: &'a [u16]) -> Self {
637         FlatSamples::U16(samples)
638     }
639 }
640 
641 impl<'a> TupleEncoding<'a> {
write_image(&self, writer: &mut dyn Write) -> ImageResult<()>642     fn write_image(&self, writer: &mut dyn Write) -> ImageResult<()> {
643         match *self {
644             TupleEncoding::PbmBits {
645                 samples: FlatSamples::U8(samples),
646                 width,
647             } => SampleWriter(writer)
648                 .write_pbm_bits(samples, width)
649                 .map_err(ImageError::IoError),
650             TupleEncoding::PbmBits {
651                 samples: FlatSamples::U16(samples),
652                 width,
653             } => SampleWriter(writer)
654                 .write_pbm_bits(samples, width)
655                 .map_err(ImageError::IoError),
656 
657             TupleEncoding::Bytes {
658                 samples: FlatSamples::U8(samples),
659             } => writer.write_all(samples).map_err(ImageError::IoError),
660             TupleEncoding::Bytes {
661                 samples: FlatSamples::U16(samples),
662             } => samples
663                 .iter()
664                 .try_for_each(|&sample| {
665                     writer
666                         .write_u16::<BigEndian>(sample)
667                         .map_err(ImageError::IoError)
668                 }),
669 
670             TupleEncoding::Ascii {
671                 samples: FlatSamples::U8(samples),
672             } => SampleWriter(writer)
673                 .write_samples_ascii(samples.iter())
674                 .map_err(ImageError::IoError),
675             TupleEncoding::Ascii {
676                 samples: FlatSamples::U16(samples),
677             } => SampleWriter(writer)
678                 .write_samples_ascii(samples.iter())
679                 .map_err(ImageError::IoError),
680         }
681     }
682 }
683