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