use std::convert::TryFrom; use std::convert::TryInto; use std::error; use std::fmt::{self, Display}; use std::io::{self, BufRead, Cursor, Read}; use std::marker::PhantomData; use std::mem; use std::num::ParseIntError; use std::str::{self, FromStr}; use super::{ArbitraryHeader, ArbitraryTuplType, BitmapHeader, GraymapHeader, PixmapHeader}; use super::{HeaderRecord, PnmHeader, PnmSubtype, SampleEncoding}; use crate::color::{ColorType, ExtendedColorType}; use crate::error::{ DecodingError, ImageError, ImageResult, UnsupportedError, UnsupportedErrorKind, }; use crate::image::{self, ImageDecoder, ImageFormat}; use crate::utils; use byteorder::{BigEndian, ByteOrder, NativeEndian}; /// All errors that can occur when attempting to parse a PNM #[derive(Debug, Clone)] enum DecoderError { /// PNM's "P[123456]" signature wrong or missing PnmMagicInvalid([u8; 2]), /// Couldn't parse the specified string as an integer from the specified source UnparsableValue(ErrorDataSource, String, ParseIntError), /// More than the exactly one allowed plane specified by the format NonAsciiByteInHeader(u8), /// The PAM header contained a non-ASCII byte NonAsciiLineInPamHeader, /// A sample string contained a non-ASCII byte NonAsciiSample, /// The byte after the P7 magic was not 0x0A NEWLINE NotNewlineAfterP7Magic(u8), /// The PNM header had too few lines UnexpectedPnmHeaderEnd, /// The specified line was specified twice HeaderLineDuplicated(PnmHeaderLine), /// The line with the specified ID was not understood HeaderLineUnknown(String), /// At least one of the required lines were missing from the header (are `None` here) /// /// Same names as [`PnmHeaderLine`](enum.PnmHeaderLine.html) #[allow(missing_docs)] HeaderLineMissing { height: Option, width: Option, depth: Option, maxval: Option, }, /// Not enough data was provided to the Decoder to decode the image InputTooShort, /// Sample raster contained unexpected byte UnexpectedByteInRaster(u8), /// Specified sample was out of bounds (e.g. >1 in B&W) SampleOutOfBounds(u8), /// The image's maxval exceeds 0xFFFF MaxvalTooBig(u32), /// The specified tuple type supports restricted depths and maxvals, those restrictions were not met InvalidDepthOrMaxval { tuple_type: ArbitraryTuplType, depth: u32, maxval: u32, }, /// The specified tuple type supports restricted depths, those restrictions were not met InvalidDepth { tuple_type: ArbitraryTuplType, depth: u32, }, /// The tuple type was not recognised by the parser TupleTypeUnrecognised, /// Overflowed the specified value when parsing Overflow, } impl Display for DecoderError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { DecoderError::PnmMagicInvalid(magic) => f.write_fmt(format_args!( "Expected magic constant for PNM: P1..P7, got [{:#04X?}, {:#04X?}]", magic[0], magic[1] )), DecoderError::UnparsableValue(src, data, err) => { f.write_fmt(format_args!("Error parsing {:?} as {}: {}", data, src, err)) } DecoderError::NonAsciiByteInHeader(c) => { f.write_fmt(format_args!("Non-ASCII character {:#04X?} in header", c)) } DecoderError::NonAsciiLineInPamHeader => f.write_str("Non-ASCII line in PAM header"), DecoderError::NonAsciiSample => { f.write_str("Non-ASCII character where sample value was expected") } DecoderError::NotNewlineAfterP7Magic(c) => f.write_fmt(format_args!( "Expected newline after P7 magic, got {:#04X?}", c )), DecoderError::UnexpectedPnmHeaderEnd => f.write_str("Unexpected end of PNM header"), DecoderError::HeaderLineDuplicated(line) => { f.write_fmt(format_args!("Duplicate {} line", line)) } DecoderError::HeaderLineUnknown(identifier) => f.write_fmt(format_args!( "Unknown header line with identifier {:?}", identifier )), DecoderError::HeaderLineMissing { height, width, depth, maxval, } => f.write_fmt(format_args!( "Missing header line: have height={:?}, width={:?}, depth={:?}, maxval={:?}", height, width, depth, maxval )), DecoderError::InputTooShort => { f.write_str("Not enough data was provided to the Decoder to decode the image") } DecoderError::UnexpectedByteInRaster(c) => f.write_fmt(format_args!( "Unexpected character {:#04X?} within sample raster", c )), DecoderError::SampleOutOfBounds(val) => { f.write_fmt(format_args!("Sample value {} outside of bounds", val)) } DecoderError::MaxvalTooBig(maxval) => { f.write_fmt(format_args!("Image MAXVAL exceeds {}: {}", 0xFFFF, maxval)) } DecoderError::InvalidDepthOrMaxval { tuple_type, depth, maxval, } => f.write_fmt(format_args!( "Invalid depth ({}) or maxval ({}) for tuple type {}", depth, maxval, tuple_type.name() )), DecoderError::InvalidDepth { tuple_type, depth } => f.write_fmt(format_args!( "Invalid depth ({}) for tuple type {}", depth, tuple_type.name() )), DecoderError::TupleTypeUnrecognised => f.write_str("Tuple type not recognized"), DecoderError::Overflow => f.write_str("Overflow when parsing value"), } } } /// Note: should `pnm` be extracted into a separate crate, /// this will need to be hidden until that crate hits version `1.0`. impl From for ImageError { fn from(e: DecoderError) -> ImageError { ImageError::Decoding(DecodingError::new(ImageFormat::Pnm.into(), e)) } } impl error::Error for DecoderError { fn source(&self) -> Option<&(dyn error::Error + 'static)> { match self { DecoderError::UnparsableValue(_, _, err) => Some(err), _ => None, } } } /// Single-value lines in a PNM header #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] enum PnmHeaderLine { /// "HEIGHT" Height, /// "WIDTH" Width, /// "DEPTH" Depth, /// "MAXVAL", a.k.a. `maxwhite` Maxval, } impl Display for PnmHeaderLine { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(match self { PnmHeaderLine::Height => "HEIGHT", PnmHeaderLine::Width => "WIDTH", PnmHeaderLine::Depth => "DEPTH", PnmHeaderLine::Maxval => "MAXVAL", }) } } /// Single-value lines in a PNM header #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] enum ErrorDataSource { /// One of the header lines Line(PnmHeaderLine), /// Value in the preamble Preamble, /// Sample/pixel data Sample, } impl Display for ErrorDataSource { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { ErrorDataSource::Line(l) => l.fmt(f), ErrorDataSource::Preamble => f.write_str("number in preamble"), ErrorDataSource::Sample => f.write_str("sample"), } } } /// Dynamic representation, represents all decodable (sample, depth) combinations. #[derive(Clone, Copy)] enum TupleType { PbmBit, BWBit, GrayU8, GrayU16, RGBU8, RGBU16, } trait Sample { fn bytelen(width: u32, height: u32, samples: u32) -> ImageResult; fn from_bytes(bytes: &[u8], row_size: usize, output_buf: &mut [u8]) -> ImageResult<()>; fn from_ascii(reader: &mut dyn Read, output_buf: &mut [u8]) -> ImageResult<()>; } struct U8; struct U16; struct PbmBit; struct BWBit; trait DecodableImageHeader { fn tuple_type(&self) -> ImageResult; } /// PNM decoder pub struct PnmDecoder { reader: R, header: PnmHeader, tuple: TupleType, } impl PnmDecoder { /// Create a new decoder that decodes from the stream ```read``` pub fn new(mut buffered_read: R) -> ImageResult> { let magic = buffered_read.read_magic_constant()?; let subtype = match magic { [b'P', b'1'] => PnmSubtype::Bitmap(SampleEncoding::Ascii), [b'P', b'2'] => PnmSubtype::Graymap(SampleEncoding::Ascii), [b'P', b'3'] => PnmSubtype::Pixmap(SampleEncoding::Ascii), [b'P', b'4'] => PnmSubtype::Bitmap(SampleEncoding::Binary), [b'P', b'5'] => PnmSubtype::Graymap(SampleEncoding::Binary), [b'P', b'6'] => PnmSubtype::Pixmap(SampleEncoding::Binary), [b'P', b'7'] => PnmSubtype::ArbitraryMap, _ => return Err(DecoderError::PnmMagicInvalid(magic).into()), }; let decoder = match subtype { PnmSubtype::Bitmap(enc) => PnmDecoder::read_bitmap_header(buffered_read, enc), PnmSubtype::Graymap(enc) => PnmDecoder::read_graymap_header(buffered_read, enc), PnmSubtype::Pixmap(enc) => PnmDecoder::read_pixmap_header(buffered_read, enc), PnmSubtype::ArbitraryMap => PnmDecoder::read_arbitrary_header(buffered_read), }?; if utils::check_dimension_overflow( decoder.dimensions().0, decoder.dimensions().1, decoder.color_type().bytes_per_pixel(), ) { return Err(ImageError::Unsupported( UnsupportedError::from_format_and_kind( ImageFormat::Pnm.into(), UnsupportedErrorKind::GenericFeature(format!( "Image dimensions ({}x{}) are too large", decoder.dimensions().0, decoder.dimensions().1 )), ), )); } Ok(decoder) } /// Extract the reader and header after an image has been read. pub fn into_inner(self) -> (R, PnmHeader) { (self.reader, self.header) } fn read_bitmap_header(mut reader: R, encoding: SampleEncoding) -> ImageResult> { let header = reader.read_bitmap_header(encoding)?; Ok(PnmDecoder { reader, tuple: TupleType::PbmBit, header: PnmHeader { decoded: HeaderRecord::Bitmap(header), encoded: None, }, }) } fn read_graymap_header(mut reader: R, encoding: SampleEncoding) -> ImageResult> { let header = reader.read_graymap_header(encoding)?; let tuple_type = header.tuple_type()?; Ok(PnmDecoder { reader, tuple: tuple_type, header: PnmHeader { decoded: HeaderRecord::Graymap(header), encoded: None, }, }) } fn read_pixmap_header(mut reader: R, encoding: SampleEncoding) -> ImageResult> { let header = reader.read_pixmap_header(encoding)?; let tuple_type = header.tuple_type()?; Ok(PnmDecoder { reader, tuple: tuple_type, header: PnmHeader { decoded: HeaderRecord::Pixmap(header), encoded: None, }, }) } fn read_arbitrary_header(mut reader: R) -> ImageResult> { let header = reader.read_arbitrary_header()?; let tuple_type = header.tuple_type()?; Ok(PnmDecoder { reader, tuple: tuple_type, header: PnmHeader { decoded: HeaderRecord::Arbitrary(header), encoded: None, }, }) } } trait HeaderReader: BufRead { /// Reads the two magic constant bytes fn read_magic_constant(&mut self) -> ImageResult<[u8; 2]> { let mut magic: [u8; 2] = [0, 0]; self.read_exact(&mut magic)?; Ok(magic) } /// Reads a string as well as a single whitespace after it, ignoring comments fn read_next_string(&mut self) -> ImageResult { let mut bytes = Vec::new(); // pair input bytes with a bool mask to remove comments let mark_comments = self.bytes().scan(true, |partof, read| { let byte = match read { Err(err) => return Some((*partof, Err(err))), Ok(byte) => byte, }; let cur_enabled = *partof && byte != b'#'; let next_enabled = cur_enabled || (byte == b'\r' || byte == b'\n'); *partof = next_enabled; Some((cur_enabled, Ok(byte))) }); for (_, byte) in mark_comments.filter(|e| e.0) { match byte { Ok(b'\t') | Ok(b'\n') | Ok(b'\x0b') | Ok(b'\x0c') | Ok(b'\r') | Ok(b' ') => { if !bytes.is_empty() { break; // We're done as we already have some content } } Ok(byte) if !byte.is_ascii() => { return Err(DecoderError::NonAsciiByteInHeader(byte).into()) } Ok(byte) => { bytes.push(byte); } Err(_) => break, } } if bytes.is_empty() { return Err(ImageError::IoError(io::ErrorKind::UnexpectedEof.into())); } if !bytes.as_slice().is_ascii() { // We have only filled the buffer with characters for which `byte.is_ascii()` holds. unreachable!("Non-ASCII character should have returned sooner") } let string = String::from_utf8(bytes) // We checked the precondition ourselves a few lines before, `bytes.as_slice().is_ascii()`. .unwrap_or_else(|_| unreachable!("Only ASCII characters should be decoded")); Ok(string) } /// Read the next line fn read_next_line(&mut self) -> ImageResult { let mut buffer = String::new(); self.read_line(&mut buffer)?; Ok(buffer) } fn read_next_u32(&mut self) -> ImageResult { let s = self.read_next_string()?; s.parse::() .map_err(|err| DecoderError::UnparsableValue(ErrorDataSource::Preamble, s, err).into()) } fn read_bitmap_header(&mut self, encoding: SampleEncoding) -> ImageResult { let width = self.read_next_u32()?; let height = self.read_next_u32()?; Ok(BitmapHeader { encoding, width, height, }) } fn read_graymap_header(&mut self, encoding: SampleEncoding) -> ImageResult { self.read_pixmap_header(encoding).map( |PixmapHeader { encoding, width, height, maxval, }| GraymapHeader { encoding, width, height, maxwhite: maxval, }, ) } fn read_pixmap_header(&mut self, encoding: SampleEncoding) -> ImageResult { let width = self.read_next_u32()?; let height = self.read_next_u32()?; let maxval = self.read_next_u32()?; Ok(PixmapHeader { encoding, width, height, maxval, }) } fn read_arbitrary_header(&mut self) -> ImageResult { fn parse_single_value_line( line_val: &mut Option, rest: &str, line: PnmHeaderLine, ) -> ImageResult<()> { if line_val.is_some() { Err(DecoderError::HeaderLineDuplicated(line).into()) } else { let v = rest.trim().parse().map_err(|err| { DecoderError::UnparsableValue(ErrorDataSource::Line(line), rest.to_owned(), err) })?; *line_val = Some(v); Ok(()) } } match self.bytes().next() { None => return Err(ImageError::IoError(io::ErrorKind::UnexpectedEof.into())), Some(Err(io)) => return Err(ImageError::IoError(io)), Some(Ok(b'\n')) => (), Some(Ok(c)) => return Err(DecoderError::NotNewlineAfterP7Magic(c).into()), } let mut line = String::new(); let mut height: Option = None; let mut width: Option = None; let mut depth: Option = None; let mut maxval: Option = None; let mut tupltype: Option = None; loop { line.truncate(0); let len = self.read_line(&mut line)?; if len == 0 { return Err(DecoderError::UnexpectedPnmHeaderEnd.into()); } if line.as_bytes()[0] == b'#' { continue; } if !line.is_ascii() { return Err(DecoderError::NonAsciiLineInPamHeader.into()); } #[allow(deprecated)] let (identifier, rest) = line .trim_left() .split_at(line.find(char::is_whitespace).unwrap_or(line.len())); match identifier { "ENDHDR" => break, "HEIGHT" => parse_single_value_line(&mut height, rest, PnmHeaderLine::Height)?, "WIDTH" => parse_single_value_line(&mut width, rest, PnmHeaderLine::Width)?, "DEPTH" => parse_single_value_line(&mut depth, rest, PnmHeaderLine::Depth)?, "MAXVAL" => parse_single_value_line(&mut maxval, rest, PnmHeaderLine::Maxval)?, "TUPLTYPE" => { let identifier = rest.trim(); if tupltype.is_some() { let appended = tupltype.take().map(|mut v| { v.push(' '); v.push_str(identifier); v }); tupltype = appended; } else { tupltype = Some(identifier.to_string()); } } _ => return Err(DecoderError::HeaderLineUnknown(identifier.to_string()).into()), } } let (h, w, d, m) = match (height, width, depth, maxval) { (Some(h), Some(w), Some(d), Some(m)) => (h, w, d, m), _ => { return Err(DecoderError::HeaderLineMissing { height, width, depth, maxval, } .into()) } }; let tupltype = match tupltype { None => None, Some(ref t) if t == "BLACKANDWHITE" => Some(ArbitraryTuplType::BlackAndWhite), Some(ref t) if t == "BLACKANDWHITE_ALPHA" => { Some(ArbitraryTuplType::BlackAndWhiteAlpha) } Some(ref t) if t == "GRAYSCALE" => Some(ArbitraryTuplType::Grayscale), Some(ref t) if t == "GRAYSCALE_ALPHA" => Some(ArbitraryTuplType::GrayscaleAlpha), Some(ref t) if t == "RGB" => Some(ArbitraryTuplType::RGB), Some(ref t) if t == "RGB_ALPHA" => Some(ArbitraryTuplType::RGBAlpha), Some(other) => Some(ArbitraryTuplType::Custom(other)), }; Ok(ArbitraryHeader { height: h, width: w, depth: d, maxval: m, tupltype, }) } } impl HeaderReader for R where R: BufRead {} /// Wrapper struct around a `Cursor>` pub struct PnmReader(Cursor>, PhantomData); impl Read for PnmReader { fn read(&mut self, buf: &mut [u8]) -> io::Result { self.0.read(buf) } fn read_to_end(&mut self, buf: &mut Vec) -> io::Result { if self.0.position() == 0 && buf.is_empty() { mem::swap(buf, self.0.get_mut()); Ok(buf.len()) } else { self.0.read_to_end(buf) } } } impl<'a, R: 'a + Read> ImageDecoder<'a> for PnmDecoder { type Reader = PnmReader; fn dimensions(&self) -> (u32, u32) { (self.header.width(), self.header.height()) } fn color_type(&self) -> ColorType { match self.tuple { TupleType::PbmBit => ColorType::L8, TupleType::BWBit => ColorType::L8, TupleType::GrayU8 => ColorType::L8, TupleType::GrayU16 => ColorType::L16, TupleType::RGBU8 => ColorType::Rgb8, TupleType::RGBU16 => ColorType::Rgb16, } } fn original_color_type(&self) -> ExtendedColorType { match self.tuple { TupleType::PbmBit => ExtendedColorType::L1, TupleType::BWBit => ExtendedColorType::L1, TupleType::GrayU8 => ExtendedColorType::L8, TupleType::GrayU16 => ExtendedColorType::L16, TupleType::RGBU8 => ExtendedColorType::Rgb8, TupleType::RGBU16 => ExtendedColorType::Rgb16, } } fn into_reader(self) -> ImageResult { Ok(PnmReader( Cursor::new(image::decoder_to_vec(self)?), PhantomData, )) } fn read_image(mut self, buf: &mut [u8]) -> ImageResult<()> { assert_eq!(u64::try_from(buf.len()), Ok(self.total_bytes())); match self.tuple { TupleType::PbmBit => self.read_samples::(1, buf), TupleType::BWBit => self.read_samples::(1, buf), TupleType::RGBU8 => self.read_samples::(3, buf), TupleType::RGBU16 => self.read_samples::(3, buf), TupleType::GrayU8 => self.read_samples::(1, buf), TupleType::GrayU16 => self.read_samples::(1, buf), } } } impl PnmDecoder { fn read_samples(&mut self, components: u32, buf: &mut [u8]) -> ImageResult<()> { match self.subtype().sample_encoding() { SampleEncoding::Binary => { let width = self.header.width(); let height = self.header.height(); let bytecount = S::bytelen(width, height, components)?; let mut bytes = vec![]; self.reader .by_ref() // This conversion is potentially lossy but unlikely and in that case we error // later anyways. .take(bytecount as u64) .read_to_end(&mut bytes)?; if bytes.len() != bytecount { return Err(DecoderError::InputTooShort.into()); } let width: usize = width.try_into().map_err(|_| DecoderError::Overflow)?; let components: usize = components.try_into().map_err(|_| DecoderError::Overflow)?; let row_size = width .checked_mul(components) .ok_or(DecoderError::Overflow)?; S::from_bytes(&bytes, row_size, buf) } SampleEncoding::Ascii => self.read_ascii::(buf), } } fn read_ascii(&mut self, output_buf: &mut [u8]) -> ImageResult<()> { Basic::from_ascii(&mut self.reader, output_buf) } /// Get the pnm subtype, depending on the magic constant contained in the header pub fn subtype(&self) -> PnmSubtype { self.header.subtype() } } fn read_separated_ascii>(reader: &mut dyn Read) -> ImageResult where T::Err: Display, { let is_separator = |v: &u8| matches! { *v, b'\t' | b'\n' | b'\x0b' | b'\x0c' | b'\r' | b' ' }; let token = reader .bytes() .skip_while(|v| v.as_ref().ok().map(is_separator).unwrap_or(false)) .take_while(|v| v.as_ref().ok().map(|c| !is_separator(c)).unwrap_or(false)) .collect::, _>>()?; if !token.is_ascii() { return Err(DecoderError::NonAsciiSample.into()); } let string = str::from_utf8(&token) // We checked the precondition ourselves a few lines before with `token.is_ascii()`. .unwrap_or_else(|_| unreachable!("Only ASCII characters should be decoded")); string.parse().map_err(|err| { DecoderError::UnparsableValue(ErrorDataSource::Sample, string.to_owned(), err).into() }) } impl Sample for U8 { fn bytelen(width: u32, height: u32, samples: u32) -> ImageResult { Ok((width * height * samples) as usize) } fn from_bytes(bytes: &[u8], _row_size: usize, output_buf: &mut [u8]) -> ImageResult<()> { output_buf.copy_from_slice(bytes); Ok(()) } fn from_ascii(reader: &mut dyn Read, output_buf: &mut [u8]) -> ImageResult<()> { for b in output_buf { *b = read_separated_ascii(reader)?; } Ok(()) } } impl Sample for U16 { fn bytelen(width: u32, height: u32, samples: u32) -> ImageResult { Ok((width * height * samples * 2) as usize) } fn from_bytes(bytes: &[u8], _row_size: usize, output_buf: &mut [u8]) -> ImageResult<()> { output_buf.copy_from_slice(bytes); for chunk in output_buf.chunks_exact_mut(2) { let v = BigEndian::read_u16(chunk); NativeEndian::write_u16(chunk, v); } Ok(()) } fn from_ascii(reader: &mut dyn Read, output_buf: &mut [u8]) -> ImageResult<()> { for chunk in output_buf.chunks_exact_mut(2) { let v = read_separated_ascii::(reader)?; NativeEndian::write_u16(chunk, v); } Ok(()) } } // The image is encoded in rows of bits, high order bits first. Any bits beyond the row bits should // be ignored. Also, contrary to rgb, black pixels are encoded as a 1 while white is 0. This will // need to be reversed for the grayscale output. impl Sample for PbmBit { fn bytelen(width: u32, height: u32, samples: u32) -> ImageResult { let count = width * samples; let linelen = (count / 8) + ((count % 8) != 0) as u32; Ok((linelen * height) as usize) } fn from_bytes(bytes: &[u8], row_size: usize, output_buf: &mut [u8]) -> ImageResult<()> { let mut expanded = utils::expand_bits(1, row_size.try_into().unwrap(), bytes); for b in expanded.iter_mut() { *b = !*b; } output_buf.copy_from_slice(&expanded); Ok(()) } fn from_ascii(reader: &mut dyn Read, output_buf: &mut [u8]) -> ImageResult<()> { let mut bytes = reader.bytes(); for b in output_buf { loop { let byte = bytes .next() .ok_or_else::(|| DecoderError::InputTooShort.into())??; match byte { b'\t' | b'\n' | b'\x0b' | b'\x0c' | b'\r' | b' ' => continue, b'0' => *b = 255, b'1' => *b = 0, c => return Err(DecoderError::UnexpectedByteInRaster(c).into()), } break; } } Ok(()) } } // Encoded just like a normal U8 but we check the values. impl Sample for BWBit { fn bytelen(width: u32, height: u32, samples: u32) -> ImageResult { U8::bytelen(width, height, samples) } fn from_bytes(bytes: &[u8], row_size: usize, output_buf: &mut [u8]) -> ImageResult<()> { U8::from_bytes(bytes, row_size, output_buf)?; if let Some(val) = output_buf.iter().find(|&val| *val > 1) { return Err(DecoderError::SampleOutOfBounds(*val).into()); } Ok(()) } fn from_ascii(_reader: &mut dyn Read, _output_buf: &mut [u8]) -> ImageResult<()> { unreachable!("BW bits from anymaps are never encoded as ASCII") } } impl DecodableImageHeader for BitmapHeader { fn tuple_type(&self) -> ImageResult { Ok(TupleType::PbmBit) } } impl DecodableImageHeader for GraymapHeader { fn tuple_type(&self) -> ImageResult { match self.maxwhite { v if v <= 0xFF => Ok(TupleType::GrayU8), v if v <= 0xFFFF => Ok(TupleType::GrayU16), _ => Err(DecoderError::MaxvalTooBig(self.maxwhite).into()), } } } impl DecodableImageHeader for PixmapHeader { fn tuple_type(&self) -> ImageResult { match self.maxval { v if v <= 0xFF => Ok(TupleType::RGBU8), v if v <= 0xFFFF => Ok(TupleType::RGBU16), _ => Err(DecoderError::MaxvalTooBig(self.maxval).into()), } } } impl DecodableImageHeader for ArbitraryHeader { fn tuple_type(&self) -> ImageResult { match self.tupltype { None if self.depth == 1 => Ok(TupleType::GrayU8), None if self.depth == 2 => Err(ImageError::Unsupported( UnsupportedError::from_format_and_kind( ImageFormat::Pnm.into(), UnsupportedErrorKind::Color(ExtendedColorType::La8), ), )), None if self.depth == 3 => Ok(TupleType::RGBU8), None if self.depth == 4 => Err(ImageError::Unsupported( UnsupportedError::from_format_and_kind( ImageFormat::Pnm.into(), UnsupportedErrorKind::Color(ExtendedColorType::Rgba8), ), )), Some(ArbitraryTuplType::BlackAndWhite) if self.maxval == 1 && self.depth == 1 => { Ok(TupleType::BWBit) } Some(ArbitraryTuplType::BlackAndWhite) => Err(DecoderError::InvalidDepthOrMaxval { tuple_type: ArbitraryTuplType::BlackAndWhite, maxval: self.maxval, depth: self.depth, } .into()), Some(ArbitraryTuplType::Grayscale) if self.depth == 1 && self.maxval <= 0xFF => { Ok(TupleType::GrayU8) } Some(ArbitraryTuplType::Grayscale) if self.depth <= 1 && self.maxval <= 0xFFFF => { Ok(TupleType::GrayU16) } Some(ArbitraryTuplType::Grayscale) => Err(DecoderError::InvalidDepthOrMaxval { tuple_type: ArbitraryTuplType::Grayscale, maxval: self.maxval, depth: self.depth, } .into()), Some(ArbitraryTuplType::RGB) if self.depth == 3 && self.maxval <= 0xFF => { Ok(TupleType::RGBU8) } Some(ArbitraryTuplType::RGB) if self.depth == 3 && self.maxval <= 0xFFFF => { Ok(TupleType::RGBU16) } Some(ArbitraryTuplType::RGB) => Err(DecoderError::InvalidDepth { tuple_type: ArbitraryTuplType::RGB, depth: self.depth, } .into()), Some(ArbitraryTuplType::BlackAndWhiteAlpha) => Err(ImageError::Unsupported( UnsupportedError::from_format_and_kind( ImageFormat::Pnm.into(), UnsupportedErrorKind::GenericFeature(format!( "Color type {}", ArbitraryTuplType::BlackAndWhiteAlpha.name() )), ), )), Some(ArbitraryTuplType::GrayscaleAlpha) => Err(ImageError::Unsupported( UnsupportedError::from_format_and_kind( ImageFormat::Pnm.into(), UnsupportedErrorKind::Color(ExtendedColorType::La8), ), )), Some(ArbitraryTuplType::RGBAlpha) => Err(ImageError::Unsupported( UnsupportedError::from_format_and_kind( ImageFormat::Pnm.into(), UnsupportedErrorKind::Color(ExtendedColorType::Rgba8), ), )), Some(ArbitraryTuplType::Custom(ref custom)) => Err(ImageError::Unsupported( UnsupportedError::from_format_and_kind( ImageFormat::Pnm.into(), UnsupportedErrorKind::GenericFeature(format!("Tuple type {:?}", custom)), ), )), None => Err(DecoderError::TupleTypeUnrecognised.into()), } } } #[cfg(test)] mod tests { use super::*; /// Tests reading of a valid blackandwhite pam #[test] fn pam_blackandwhite() { let pamdata = b"P7 WIDTH 4 HEIGHT 4 DEPTH 1 MAXVAL 1 TUPLTYPE BLACKANDWHITE # Comment line ENDHDR \x01\x00\x00\x01\x01\x00\x00\x01\x01\x00\x00\x01\x01\x00\x00\x01"; let decoder = PnmDecoder::new(&pamdata[..]).unwrap(); assert_eq!(decoder.color_type(), ColorType::L8); assert_eq!(decoder.original_color_type(), ExtendedColorType::L1); assert_eq!(decoder.dimensions(), (4, 4)); assert_eq!(decoder.subtype(), PnmSubtype::ArbitraryMap); let mut image = vec![0; decoder.total_bytes() as usize]; decoder.read_image(&mut image).unwrap(); assert_eq!( image, vec![ 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01 ] ); match PnmDecoder::new(&pamdata[..]).unwrap().into_inner() { ( _, PnmHeader { decoded: HeaderRecord::Arbitrary(ArbitraryHeader { width: 4, height: 4, maxval: 1, depth: 1, tupltype: Some(ArbitraryTuplType::BlackAndWhite), }), encoded: _, }, ) => (), _ => panic!("Decoded header is incorrect"), } } /// Tests reading of a valid grayscale pam #[test] fn pam_grayscale() { let pamdata = b"P7 WIDTH 4 HEIGHT 4 DEPTH 1 MAXVAL 255 TUPLTYPE GRAYSCALE # Comment line ENDHDR \xde\xad\xbe\xef\xde\xad\xbe\xef\xde\xad\xbe\xef\xde\xad\xbe\xef"; let decoder = PnmDecoder::new(&pamdata[..]).unwrap(); assert_eq!(decoder.color_type(), ColorType::L8); assert_eq!(decoder.dimensions(), (4, 4)); assert_eq!(decoder.subtype(), PnmSubtype::ArbitraryMap); let mut image = vec![0; decoder.total_bytes() as usize]; decoder.read_image(&mut image).unwrap(); assert_eq!( image, vec![ 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef ] ); match PnmDecoder::new(&pamdata[..]).unwrap().into_inner() { ( _, PnmHeader { decoded: HeaderRecord::Arbitrary(ArbitraryHeader { width: 4, height: 4, depth: 1, maxval: 255, tupltype: Some(ArbitraryTuplType::Grayscale), }), encoded: _, }, ) => (), _ => panic!("Decoded header is incorrect"), } } /// Tests reading of a valid rgb pam #[test] fn pam_rgb() { let pamdata = b"P7 # Comment line MAXVAL 255 TUPLTYPE RGB DEPTH 3 WIDTH 2 HEIGHT 2 ENDHDR \xde\xad\xbe\xef\xde\xad\xbe\xef\xde\xad\xbe\xef"; let decoder = PnmDecoder::new(&pamdata[..]).unwrap(); assert_eq!(decoder.color_type(), ColorType::Rgb8); assert_eq!(decoder.dimensions(), (2, 2)); assert_eq!(decoder.subtype(), PnmSubtype::ArbitraryMap); let mut image = vec![0; decoder.total_bytes() as usize]; decoder.read_image(&mut image).unwrap(); assert_eq!( image, vec![0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef] ); match PnmDecoder::new(&pamdata[..]).unwrap().into_inner() { ( _, PnmHeader { decoded: HeaderRecord::Arbitrary(ArbitraryHeader { maxval: 255, tupltype: Some(ArbitraryTuplType::RGB), depth: 3, width: 2, height: 2, }), encoded: _, }, ) => (), _ => panic!("Decoded header is incorrect"), } } #[test] fn pbm_binary() { // The data contains two rows of the image (each line is padded to the full byte). For // comments on its format, see documentation of `impl SampleType for PbmBit`. let pbmbinary = [&b"P4 6 2\n"[..], &[0b01101100 as u8, 0b10110111]].concat(); let decoder = PnmDecoder::new(&pbmbinary[..]).unwrap(); assert_eq!(decoder.color_type(), ColorType::L8); assert_eq!(decoder.original_color_type(), ExtendedColorType::L1); assert_eq!(decoder.dimensions(), (6, 2)); assert_eq!( decoder.subtype(), PnmSubtype::Bitmap(SampleEncoding::Binary) ); let mut image = vec![0; decoder.total_bytes() as usize]; decoder.read_image(&mut image).unwrap(); assert_eq!(image, vec![255, 0, 0, 255, 0, 0, 0, 255, 0, 0, 255, 0]); match PnmDecoder::new(&pbmbinary[..]).unwrap().into_inner() { ( _, PnmHeader { decoded: HeaderRecord::Bitmap(BitmapHeader { encoding: SampleEncoding::Binary, width: 6, height: 2, }), encoded: _, }, ) => (), _ => panic!("Decoded header is incorrect"), } } /// A previous infinite loop. #[test] fn pbm_binary_ascii_termination() { use std::io::{BufReader, Cursor, Error, ErrorKind, Read, Result}; struct FailRead(Cursor<&'static [u8]>); impl Read for FailRead { fn read(&mut self, buf: &mut [u8]) -> Result { match self.0.read(buf) { Ok(n) if n > 0 => Ok(n), _ => Err(Error::new( ErrorKind::BrokenPipe, "Simulated broken pipe error", )), } } } let pbmbinary = BufReader::new(FailRead(Cursor::new(b"P1 1 1\n"))); let decoder = PnmDecoder::new(pbmbinary).unwrap(); let mut image = vec![0; decoder.total_bytes() as usize]; decoder .read_image(&mut image) .expect_err("Image is malformed"); } #[test] fn pbm_ascii() { // The data contains two rows of the image (each line is padded to the full byte). For // comments on its format, see documentation of `impl SampleType for PbmBit`. Tests all // whitespace characters that should be allowed (the 6 characters according to POSIX). let pbmbinary = b"P1 6 2\n 0 1 1 0 1 1\n1 0 1 1 0\t\n\x0b\x0c\r1"; let decoder = PnmDecoder::new(&pbmbinary[..]).unwrap(); assert_eq!(decoder.color_type(), ColorType::L8); assert_eq!(decoder.original_color_type(), ExtendedColorType::L1); assert_eq!(decoder.dimensions(), (6, 2)); assert_eq!(decoder.subtype(), PnmSubtype::Bitmap(SampleEncoding::Ascii)); let mut image = vec![0; decoder.total_bytes() as usize]; decoder.read_image(&mut image).unwrap(); assert_eq!(image, vec![255, 0, 0, 255, 0, 0, 0, 255, 0, 0, 255, 0]); match PnmDecoder::new(&pbmbinary[..]).unwrap().into_inner() { ( _, PnmHeader { decoded: HeaderRecord::Bitmap(BitmapHeader { encoding: SampleEncoding::Ascii, width: 6, height: 2, }), encoded: _, }, ) => (), _ => panic!("Decoded header is incorrect"), } } #[test] fn pbm_ascii_nospace() { // The data contains two rows of the image (each line is padded to the full byte). Notably, // it is completely within specification for the ascii data not to contain separating // whitespace for the pbm format or any mix. let pbmbinary = b"P1 6 2\n011011101101"; let decoder = PnmDecoder::new(&pbmbinary[..]).unwrap(); assert_eq!(decoder.color_type(), ColorType::L8); assert_eq!(decoder.original_color_type(), ExtendedColorType::L1); assert_eq!(decoder.dimensions(), (6, 2)); assert_eq!(decoder.subtype(), PnmSubtype::Bitmap(SampleEncoding::Ascii)); let mut image = vec![0; decoder.total_bytes() as usize]; decoder.read_image(&mut image).unwrap(); assert_eq!(image, vec![255, 0, 0, 255, 0, 0, 0, 255, 0, 0, 255, 0]); match PnmDecoder::new(&pbmbinary[..]).unwrap().into_inner() { ( _, PnmHeader { decoded: HeaderRecord::Bitmap(BitmapHeader { encoding: SampleEncoding::Ascii, width: 6, height: 2, }), encoded: _, }, ) => (), _ => panic!("Decoded header is incorrect"), } } #[test] fn pgm_binary() { // The data contains two rows of the image (each line is padded to the full byte). For // comments on its format, see documentation of `impl SampleType for PbmBit`. let elements = (0..16).collect::>(); let pbmbinary = [&b"P5 4 4 255\n"[..], &elements].concat(); let decoder = PnmDecoder::new(&pbmbinary[..]).unwrap(); assert_eq!(decoder.color_type(), ColorType::L8); assert_eq!(decoder.dimensions(), (4, 4)); assert_eq!( decoder.subtype(), PnmSubtype::Graymap(SampleEncoding::Binary) ); let mut image = vec![0; decoder.total_bytes() as usize]; decoder.read_image(&mut image).unwrap(); assert_eq!(image, elements); match PnmDecoder::new(&pbmbinary[..]).unwrap().into_inner() { ( _, PnmHeader { decoded: HeaderRecord::Graymap(GraymapHeader { encoding: SampleEncoding::Binary, width: 4, height: 4, maxwhite: 255, }), encoded: _, }, ) => (), _ => panic!("Decoded header is incorrect"), } } #[test] fn pgm_ascii() { // The data contains two rows of the image (each line is padded to the full byte). For // comments on its format, see documentation of `impl SampleType for PbmBit`. let pbmbinary = b"P2 4 4 255\n 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15"; let decoder = PnmDecoder::new(&pbmbinary[..]).unwrap(); assert_eq!(decoder.color_type(), ColorType::L8); assert_eq!(decoder.dimensions(), (4, 4)); assert_eq!( decoder.subtype(), PnmSubtype::Graymap(SampleEncoding::Ascii) ); let mut image = vec![0; decoder.total_bytes() as usize]; decoder.read_image(&mut image).unwrap(); assert_eq!(image, (0..16).collect::>()); match PnmDecoder::new(&pbmbinary[..]).unwrap().into_inner() { ( _, PnmHeader { decoded: HeaderRecord::Graymap(GraymapHeader { encoding: SampleEncoding::Ascii, width: 4, height: 4, maxwhite: 255, }), encoded: _, }, ) => (), _ => panic!("Decoded header is incorrect"), } } #[test] fn dimension_overflow() { let pamdata = b"P7 # Comment line MAXVAL 255 TUPLTYPE RGB DEPTH 3 WIDTH 4294967295 HEIGHT 4294967295 ENDHDR \xde\xad\xbe\xef\xde\xad\xbe\xef\xde\xad\xbe\xef"; assert!(PnmDecoder::new(&pamdata[..]).is_err()); } #[test] fn issue_1508() { let _ = crate::load_from_memory(b"P391919 16999 1 1 9 919 16999 1 9999 999* 99999 N"); } #[test] fn issue_1616_overflow() { let data = vec![ 80, 54, 10, 52, 50, 57, 52, 56, 50, 57, 52, 56, 35, 56, 10, 52, 10, 48, 10, 12, 12, 56, ]; // Validate: we have a header. Note: we might already calculate that this will fail but // then we could not return information about the header to the caller. let decoder = PnmDecoder::new(&data[..]).unwrap(); let mut image = vec![0; decoder.total_bytes() as usize]; let _ = decoder.read_image(&mut image); } }