diff options
author | Valentin Popov <valentin@popov.link> | 2024-01-08 00:21:28 +0300 |
---|---|---|
committer | Valentin Popov <valentin@popov.link> | 2024-01-08 00:21:28 +0300 |
commit | 1b6a04ca5504955c571d1c97504fb45ea0befee4 (patch) | |
tree | 7579f518b23313e8a9748a88ab6173d5e030b227 /vendor/image/src/codecs/pnm | |
parent | 5ecd8cf2cba827454317368b68571df0d13d7842 (diff) | |
download | fparkan-1b6a04ca5504955c571d1c97504fb45ea0befee4.tar.xz fparkan-1b6a04ca5504955c571d1c97504fb45ea0befee4.zip |
Initial vendor packages
Signed-off-by: Valentin Popov <valentin@popov.link>
Diffstat (limited to 'vendor/image/src/codecs/pnm')
-rw-r--r-- | vendor/image/src/codecs/pnm/autobreak.rs | 124 | ||||
-rw-r--r-- | vendor/image/src/codecs/pnm/decoder.rs | 1272 | ||||
-rw-r--r-- | vendor/image/src/codecs/pnm/encoder.rs | 673 | ||||
-rw-r--r-- | vendor/image/src/codecs/pnm/header.rs | 354 | ||||
-rw-r--r-- | vendor/image/src/codecs/pnm/mod.rs | 184 |
5 files changed, 2607 insertions, 0 deletions
diff --git a/vendor/image/src/codecs/pnm/autobreak.rs b/vendor/image/src/codecs/pnm/autobreak.rs new file mode 100644 index 0000000..cea2cd8 --- /dev/null +++ b/vendor/image/src/codecs/pnm/autobreak.rs @@ -0,0 +1,124 @@ +//! Insert line breaks between written buffers when they would overflow the line length. +use std::io; + +// The pnm standard says to insert line breaks after 70 characters. Assumes that no line breaks +// are actually written. We have to be careful to fully commit buffers or not commit them at all, +// otherwise we might insert a newline in the middle of a token. +pub(crate) struct AutoBreak<W: io::Write> { + wrapped: W, + line_capacity: usize, + line: Vec<u8>, + has_newline: bool, + panicked: bool, // see https://github.com/rust-lang/rust/issues/30888 +} + +impl<W: io::Write> AutoBreak<W> { + pub(crate) fn new(writer: W, line_capacity: usize) -> Self { + AutoBreak { + wrapped: writer, + line_capacity, + line: Vec::with_capacity(line_capacity + 1), + has_newline: false, + panicked: false, + } + } + + fn flush_buf(&mut self) -> io::Result<()> { + // from BufWriter + let mut written = 0; + let len = self.line.len(); + let mut ret = Ok(()); + while written < len { + self.panicked = true; + let r = self.wrapped.write(&self.line[written..]); + self.panicked = false; + match r { + Ok(0) => { + ret = Err(io::Error::new( + io::ErrorKind::WriteZero, + "failed to write the buffered data", + )); + break; + } + Ok(n) => written += n, + Err(ref e) if e.kind() == io::ErrorKind::Interrupted => {} + Err(e) => { + ret = Err(e); + break; + } + } + } + if written > 0 { + self.line.drain(..written); + } + ret + } +} + +impl<W: io::Write> io::Write for AutoBreak<W> { + fn write(&mut self, buffer: &[u8]) -> io::Result<usize> { + if self.has_newline { + self.flush()?; + self.has_newline = false; + } + + if !self.line.is_empty() && self.line.len() + buffer.len() > self.line_capacity { + self.line.push(b'\n'); + self.has_newline = true; + self.flush()?; + self.has_newline = false; + } + + self.line.extend_from_slice(buffer); + Ok(buffer.len()) + } + + fn flush(&mut self) -> io::Result<()> { + self.flush_buf()?; + self.wrapped.flush() + } +} + +impl<W: io::Write> Drop for AutoBreak<W> { + fn drop(&mut self) { + if !self.panicked { + let _r = self.flush_buf(); + // internal writer flushed automatically by Drop + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::io::Write; + + #[test] + fn test_aligned_writes() { + let mut output = Vec::new(); + + { + let mut writer = AutoBreak::new(&mut output, 10); + writer.write_all(b"0123456789").unwrap(); + writer.write_all(b"0123456789").unwrap(); + } + + assert_eq!(output.as_slice(), b"0123456789\n0123456789"); + } + + #[test] + fn test_greater_writes() { + let mut output = Vec::new(); + + { + let mut writer = AutoBreak::new(&mut output, 10); + writer.write_all(b"012").unwrap(); + writer.write_all(b"345").unwrap(); + writer.write_all(b"0123456789").unwrap(); + writer.write_all(b"012345678910").unwrap(); + writer.write_all(b"_").unwrap(); + } + + assert_eq!(output.as_slice(), b"012345\n0123456789\n012345678910\n_"); + } +} diff --git a/vendor/image/src/codecs/pnm/decoder.rs b/vendor/image/src/codecs/pnm/decoder.rs new file mode 100644 index 0000000..a495871 --- /dev/null +++ b/vendor/image/src/codecs/pnm/decoder.rs @@ -0,0 +1,1272 @@ +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<u32>, + width: Option<u32>, + depth: Option<u32>, + maxval: Option<u32>, + }, + + /// 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<DecoderError> 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<usize>; + 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<TupleType>; +} + +/// PNM decoder +pub struct PnmDecoder<R> { + reader: R, + header: PnmHeader, + tuple: TupleType, +} + +impl<R: BufRead> PnmDecoder<R> { + /// Create a new decoder that decodes from the stream ```read``` + pub fn new(mut buffered_read: R) -> ImageResult<PnmDecoder<R>> { + 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<PnmDecoder<R>> { + 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<PnmDecoder<R>> { + 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<PnmDecoder<R>> { + 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<PnmDecoder<R>> { + 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<String> { + 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<String> { + let mut buffer = String::new(); + self.read_line(&mut buffer)?; + Ok(buffer) + } + + fn read_next_u32(&mut self) -> ImageResult<u32> { + let s = self.read_next_string()?; + s.parse::<u32>() + .map_err(|err| DecoderError::UnparsableValue(ErrorDataSource::Preamble, s, err).into()) + } + + fn read_bitmap_header(&mut self, encoding: SampleEncoding) -> ImageResult<BitmapHeader> { + 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<GraymapHeader> { + 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<PixmapHeader> { + 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<ArbitraryHeader> { + fn parse_single_value_line( + line_val: &mut Option<u32>, + 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<u32> = None; + let mut width: Option<u32> = None; + let mut depth: Option<u32> = None; + let mut maxval: Option<u32> = None; + let mut tupltype: Option<String> = 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<R> HeaderReader for R where R: BufRead {} + +/// Wrapper struct around a `Cursor<Vec<u8>>` +pub struct PnmReader<R>(Cursor<Vec<u8>>, PhantomData<R>); +impl<R> Read for PnmReader<R> { + fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { + self.0.read(buf) + } + fn read_to_end(&mut self, buf: &mut Vec<u8>) -> io::Result<usize> { + 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<R> { + type Reader = PnmReader<R>; + + 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<Self::Reader> { + 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::<PbmBit>(1, buf), + TupleType::BWBit => self.read_samples::<BWBit>(1, buf), + TupleType::RGBU8 => self.read_samples::<U8>(3, buf), + TupleType::RGBU16 => self.read_samples::<U16>(3, buf), + TupleType::GrayU8 => self.read_samples::<U8>(1, buf), + TupleType::GrayU16 => self.read_samples::<U16>(1, buf), + } + } +} + +impl<R: Read> PnmDecoder<R> { + fn read_samples<S: Sample>(&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::<S>(buf), + } + } + + fn read_ascii<Basic: Sample>(&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<T: FromStr<Err = ParseIntError>>(reader: &mut dyn Read) -> ImageResult<T> +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::<Result<Vec<u8>, _>>()?; + + 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<usize> { + 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<usize> { + 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::<u16>(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<usize> { + 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::<ImageError, _>(|| 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<usize> { + 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<TupleType> { + Ok(TupleType::PbmBit) + } +} + +impl DecodableImageHeader for GraymapHeader { + fn tuple_type(&self) -> ImageResult<TupleType> { + 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<TupleType> { + 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<TupleType> { + 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<usize> { + 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::<Vec<_>>(); + 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::<Vec<_>>()); + 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); + } +} diff --git a/vendor/image/src/codecs/pnm/encoder.rs b/vendor/image/src/codecs/pnm/encoder.rs new file mode 100644 index 0000000..9f823d0 --- /dev/null +++ b/vendor/image/src/codecs/pnm/encoder.rs @@ -0,0 +1,673 @@ +//! Encoding of PNM Images +use std::fmt; +use std::io; + +use std::io::Write; + +use super::AutoBreak; +use super::{ArbitraryHeader, ArbitraryTuplType, BitmapHeader, GraymapHeader, PixmapHeader}; +use super::{HeaderRecord, PnmHeader, PnmSubtype, SampleEncoding}; +use crate::color::{ColorType, ExtendedColorType}; +use crate::error::{ + ImageError, ImageResult, ParameterError, ParameterErrorKind, UnsupportedError, + UnsupportedErrorKind, +}; +use crate::image::{ImageEncoder, ImageFormat}; + +use byteorder::{BigEndian, WriteBytesExt}; + +enum HeaderStrategy { + Dynamic, + Subtype(PnmSubtype), + Chosen(PnmHeader), +} + +#[derive(Clone, Copy)] +pub enum FlatSamples<'a> { + U8(&'a [u8]), + U16(&'a [u16]), +} + +/// Encodes images to any of the `pnm` image formats. +pub struct PnmEncoder<W: Write> { + writer: W, + header: HeaderStrategy, +} + +/// Encapsulate the checking system in the type system. Non of the fields are actually accessed +/// but requiring them forces us to validly construct the struct anyways. +struct CheckedImageBuffer<'a> { + _image: FlatSamples<'a>, + _width: u32, + _height: u32, + _color: ExtendedColorType, +} + +// Check the header against the buffer. Each struct produces the next after a check. +struct UncheckedHeader<'a> { + header: &'a PnmHeader, +} + +struct CheckedDimensions<'a> { + unchecked: UncheckedHeader<'a>, + width: u32, + height: u32, +} + +struct CheckedHeaderColor<'a> { + dimensions: CheckedDimensions<'a>, + color: ExtendedColorType, +} + +struct CheckedHeader<'a> { + color: CheckedHeaderColor<'a>, + encoding: TupleEncoding<'a>, + _image: CheckedImageBuffer<'a>, +} + +enum TupleEncoding<'a> { + PbmBits { + samples: FlatSamples<'a>, + width: u32, + }, + Ascii { + samples: FlatSamples<'a>, + }, + Bytes { + samples: FlatSamples<'a>, + }, +} + +impl<W: Write> PnmEncoder<W> { + /// Create new PnmEncoder from the `writer`. + /// + /// The encoded images will have some `pnm` format. If more control over the image type is + /// required, use either one of `with_subtype` or `with_header`. For more information on the + /// behaviour, see `with_dynamic_header`. + pub fn new(writer: W) -> Self { + PnmEncoder { + writer, + header: HeaderStrategy::Dynamic, + } + } + + /// Encode a specific pnm subtype image. + /// + /// The magic number and encoding type will be chosen as provided while the rest of the header + /// data will be generated dynamically. Trying to encode incompatible images (e.g. encoding an + /// RGB image as Graymap) will result in an error. + /// + /// This will overwrite the effect of earlier calls to `with_header` and `with_dynamic_header`. + pub fn with_subtype(self, subtype: PnmSubtype) -> Self { + PnmEncoder { + writer: self.writer, + header: HeaderStrategy::Subtype(subtype), + } + } + + /// Enforce the use of a chosen header. + /// + /// While this option gives the most control over the actual written data, the encoding process + /// will error in case the header data and image parameters do not agree. It is the users + /// obligation to ensure that the width and height are set accordingly, for example. + /// + /// Choose this option if you want a lossless decoding/encoding round trip. + /// + /// This will overwrite the effect of earlier calls to `with_subtype` and `with_dynamic_header`. + pub fn with_header(self, header: PnmHeader) -> Self { + PnmEncoder { + writer: self.writer, + header: HeaderStrategy::Chosen(header), + } + } + + /// Create the header dynamically for each image. + /// + /// This is the default option upon creation of the encoder. With this, most images should be + /// encodable but the specific format chosen is out of the users control. The pnm subtype is + /// chosen arbitrarily by the library. + /// + /// This will overwrite the effect of earlier calls to `with_subtype` and `with_header`. + pub fn with_dynamic_header(self) -> Self { + PnmEncoder { + writer: self.writer, + header: HeaderStrategy::Dynamic, + } + } + + /// Encode an image whose samples are represented as `u8`. + /// + /// Some `pnm` subtypes are incompatible with some color options, a chosen header most + /// certainly with any deviation from the original decoded image. + pub fn encode<'s, S>( + &mut self, + image: S, + width: u32, + height: u32, + color: ColorType, + ) -> ImageResult<()> + where + S: Into<FlatSamples<'s>>, + { + let image = image.into(); + match self.header { + HeaderStrategy::Dynamic => { + self.write_dynamic_header(image, width, height, color.into()) + } + HeaderStrategy::Subtype(subtype) => { + self.write_subtyped_header(subtype, image, width, height, color.into()) + } + HeaderStrategy::Chosen(ref header) => Self::write_with_header( + &mut self.writer, + header, + image, + width, + height, + color.into(), + ), + } + } + + /// Choose any valid pnm format that the image can be expressed in and write its header. + /// + /// Returns how the body should be written if successful. + fn write_dynamic_header( + &mut self, + image: FlatSamples, + width: u32, + height: u32, + color: ExtendedColorType, + ) -> ImageResult<()> { + let depth = u32::from(color.channel_count()); + let (maxval, tupltype) = match color { + ExtendedColorType::L1 => (1, ArbitraryTuplType::BlackAndWhite), + ExtendedColorType::L8 => (0xff, ArbitraryTuplType::Grayscale), + ExtendedColorType::L16 => (0xffff, ArbitraryTuplType::Grayscale), + ExtendedColorType::La1 => (1, ArbitraryTuplType::BlackAndWhiteAlpha), + ExtendedColorType::La8 => (0xff, ArbitraryTuplType::GrayscaleAlpha), + ExtendedColorType::La16 => (0xffff, ArbitraryTuplType::GrayscaleAlpha), + ExtendedColorType::Rgb8 => (0xff, ArbitraryTuplType::RGB), + ExtendedColorType::Rgb16 => (0xffff, ArbitraryTuplType::RGB), + ExtendedColorType::Rgba8 => (0xff, ArbitraryTuplType::RGBAlpha), + ExtendedColorType::Rgba16 => (0xffff, ArbitraryTuplType::RGBAlpha), + _ => { + return Err(ImageError::Unsupported( + UnsupportedError::from_format_and_kind( + ImageFormat::Pnm.into(), + UnsupportedErrorKind::Color(color), + ), + )) + } + }; + + let header = PnmHeader { + decoded: HeaderRecord::Arbitrary(ArbitraryHeader { + width, + height, + depth, + maxval, + tupltype: Some(tupltype), + }), + encoded: None, + }; + + Self::write_with_header(&mut self.writer, &header, image, width, height, color) + } + + /// Try to encode the image with the chosen format, give its corresponding pixel encoding type. + fn write_subtyped_header( + &mut self, + subtype: PnmSubtype, + image: FlatSamples, + width: u32, + height: u32, + color: ExtendedColorType, + ) -> ImageResult<()> { + let header = match (subtype, color) { + (PnmSubtype::ArbitraryMap, color) => { + return self.write_dynamic_header(image, width, height, color) + } + (PnmSubtype::Pixmap(encoding), ExtendedColorType::Rgb8) => PnmHeader { + decoded: HeaderRecord::Pixmap(PixmapHeader { + encoding, + width, + height, + maxval: 255, + }), + encoded: None, + }, + (PnmSubtype::Graymap(encoding), ExtendedColorType::L8) => PnmHeader { + decoded: HeaderRecord::Graymap(GraymapHeader { + encoding, + width, + height, + maxwhite: 255, + }), + encoded: None, + }, + (PnmSubtype::Bitmap(encoding), ExtendedColorType::L8) + | (PnmSubtype::Bitmap(encoding), ExtendedColorType::L1) => PnmHeader { + decoded: HeaderRecord::Bitmap(BitmapHeader { + encoding, + width, + height, + }), + encoded: None, + }, + (_, _) => { + return Err(ImageError::Parameter(ParameterError::from_kind( + ParameterErrorKind::Generic( + "Color type can not be represented in the chosen format".to_owned(), + ), + ))); + } + }; + + Self::write_with_header(&mut self.writer, &header, image, width, height, color) + } + + /// Try to encode the image with the chosen header, checking if values are correct. + /// + /// Returns how the body should be written if successful. + fn write_with_header( + writer: &mut dyn Write, + header: &PnmHeader, + image: FlatSamples, + width: u32, + height: u32, + color: ExtendedColorType, + ) -> ImageResult<()> { + let unchecked = UncheckedHeader { header }; + + unchecked + .check_header_dimensions(width, height)? + .check_header_color(color)? + .check_sample_values(image)? + .write_header(writer)? + .write_image(writer) + } +} + +impl<W: Write> ImageEncoder for PnmEncoder<W> { + fn write_image( + mut self, + buf: &[u8], + width: u32, + height: u32, + color_type: ColorType, + ) -> ImageResult<()> { + self.encode(buf, width, height, color_type) + } +} + +impl<'a> CheckedImageBuffer<'a> { + fn check( + image: FlatSamples<'a>, + width: u32, + height: u32, + color: ExtendedColorType, + ) -> ImageResult<CheckedImageBuffer<'a>> { + let components = color.channel_count() as usize; + let uwidth = width as usize; + let uheight = height as usize; + let expected_len = components + .checked_mul(uwidth) + .and_then(|v| v.checked_mul(uheight)); + if Some(image.len()) != expected_len { + // Image buffer does not correspond to size and colour. + return Err(ImageError::Parameter(ParameterError::from_kind( + ParameterErrorKind::DimensionMismatch, + ))); + } + Ok(CheckedImageBuffer { + _image: image, + _width: width, + _height: height, + _color: color, + }) + } +} + +impl<'a> UncheckedHeader<'a> { + fn check_header_dimensions( + self, + width: u32, + height: u32, + ) -> ImageResult<CheckedDimensions<'a>> { + if self.header.width() != width || self.header.height() != height { + // Chosen header does not match Image dimensions. + return Err(ImageError::Parameter(ParameterError::from_kind( + ParameterErrorKind::DimensionMismatch, + ))); + } + + Ok(CheckedDimensions { + unchecked: self, + width, + height, + }) + } +} + +impl<'a> CheckedDimensions<'a> { + // Check color compatibility with the header. This will only error when we are certain that + // the combination is bogus (e.g. combining Pixmap and Palette) but allows uncertain + // combinations (basically a ArbitraryTuplType::Custom with any color of fitting depth). + fn check_header_color(self, color: ExtendedColorType) -> ImageResult<CheckedHeaderColor<'a>> { + let components = u32::from(color.channel_count()); + + match *self.unchecked.header { + PnmHeader { + decoded: HeaderRecord::Bitmap(_), + .. + } => match color { + ExtendedColorType::L1 | ExtendedColorType::L8 | ExtendedColorType::L16 => (), + _ => { + return Err(ImageError::Parameter(ParameterError::from_kind( + ParameterErrorKind::Generic( + "PBM format only support luma color types".to_owned(), + ), + ))) + } + }, + PnmHeader { + decoded: HeaderRecord::Graymap(_), + .. + } => match color { + ExtendedColorType::L1 | ExtendedColorType::L8 | ExtendedColorType::L16 => (), + _ => { + return Err(ImageError::Parameter(ParameterError::from_kind( + ParameterErrorKind::Generic( + "PGM format only support luma color types".to_owned(), + ), + ))) + } + }, + PnmHeader { + decoded: HeaderRecord::Pixmap(_), + .. + } => match color { + ExtendedColorType::Rgb8 => (), + _ => { + return Err(ImageError::Parameter(ParameterError::from_kind( + ParameterErrorKind::Generic( + "PPM format only support ExtendedColorType::Rgb8".to_owned(), + ), + ))) + } + }, + PnmHeader { + decoded: + HeaderRecord::Arbitrary(ArbitraryHeader { + depth, + ref tupltype, + .. + }), + .. + } => match (tupltype, color) { + (&Some(ArbitraryTuplType::BlackAndWhite), ExtendedColorType::L1) => (), + (&Some(ArbitraryTuplType::BlackAndWhiteAlpha), ExtendedColorType::La8) => (), + + (&Some(ArbitraryTuplType::Grayscale), ExtendedColorType::L1) => (), + (&Some(ArbitraryTuplType::Grayscale), ExtendedColorType::L8) => (), + (&Some(ArbitraryTuplType::Grayscale), ExtendedColorType::L16) => (), + (&Some(ArbitraryTuplType::GrayscaleAlpha), ExtendedColorType::La8) => (), + + (&Some(ArbitraryTuplType::RGB), ExtendedColorType::Rgb8) => (), + (&Some(ArbitraryTuplType::RGBAlpha), ExtendedColorType::Rgba8) => (), + + (&None, _) if depth == components => (), + (&Some(ArbitraryTuplType::Custom(_)), _) if depth == components => (), + _ if depth != components => { + return Err(ImageError::Parameter(ParameterError::from_kind( + ParameterErrorKind::Generic(format!( + "Depth mismatch: header {} vs. color {}", + depth, components + )), + ))) + } + _ => { + return Err(ImageError::Parameter(ParameterError::from_kind( + ParameterErrorKind::Generic( + "Invalid color type for selected PAM color type".to_owned(), + ), + ))) + } + }, + } + + Ok(CheckedHeaderColor { + dimensions: self, + color, + }) + } +} + +impl<'a> CheckedHeaderColor<'a> { + fn check_sample_values(self, image: FlatSamples<'a>) -> ImageResult<CheckedHeader<'a>> { + let header_maxval = match self.dimensions.unchecked.header.decoded { + HeaderRecord::Bitmap(_) => 1, + HeaderRecord::Graymap(GraymapHeader { maxwhite, .. }) => maxwhite, + HeaderRecord::Pixmap(PixmapHeader { maxval, .. }) => maxval, + HeaderRecord::Arbitrary(ArbitraryHeader { maxval, .. }) => maxval, + }; + + // We trust the image color bit count to be correct at least. + let max_sample = match self.color { + ExtendedColorType::Unknown(n) if n <= 16 => (1 << n) - 1, + ExtendedColorType::L1 => 1, + ExtendedColorType::L8 + | ExtendedColorType::La8 + | ExtendedColorType::Rgb8 + | ExtendedColorType::Rgba8 + | ExtendedColorType::Bgr8 + | ExtendedColorType::Bgra8 => 0xff, + ExtendedColorType::L16 + | ExtendedColorType::La16 + | ExtendedColorType::Rgb16 + | ExtendedColorType::Rgba16 => 0xffff, + _ => { + // Unsupported target color type. + return Err(ImageError::Unsupported( + UnsupportedError::from_format_and_kind( + ImageFormat::Pnm.into(), + UnsupportedErrorKind::Color(self.color), + ), + )); + } + }; + + // Avoid the performance heavy check if possible, e.g. if the header has been chosen by us. + if header_maxval < max_sample && !image.all_smaller(header_maxval) { + // Sample value greater than allowed for chosen header. + return Err(ImageError::Unsupported( + UnsupportedError::from_format_and_kind( + ImageFormat::Pnm.into(), + UnsupportedErrorKind::GenericFeature( + "Sample value greater than allowed for chosen header".to_owned(), + ), + ), + )); + } + + let encoding = image.encoding_for(&self.dimensions.unchecked.header.decoded); + + let image = CheckedImageBuffer::check( + image, + self.dimensions.width, + self.dimensions.height, + self.color, + )?; + + Ok(CheckedHeader { + color: self, + encoding, + _image: image, + }) + } +} + +impl<'a> CheckedHeader<'a> { + fn write_header(self, writer: &mut dyn Write) -> ImageResult<TupleEncoding<'a>> { + self.header().write(writer)?; + Ok(self.encoding) + } + + fn header(&self) -> &PnmHeader { + self.color.dimensions.unchecked.header + } +} + +struct SampleWriter<'a>(&'a mut dyn Write); + +impl<'a> SampleWriter<'a> { + fn write_samples_ascii<V>(self, samples: V) -> io::Result<()> + where + V: Iterator, + V::Item: fmt::Display, + { + let mut auto_break_writer = AutoBreak::new(self.0, 70); + for value in samples { + write!(auto_break_writer, "{} ", value)?; + } + auto_break_writer.flush() + } + + fn write_pbm_bits<V>(self, samples: &[V], width: u32) -> io::Result<()> + /* Default gives 0 for all primitives. TODO: replace this with `Zeroable` once it hits stable */ + where + V: Default + Eq + Copy, + { + // The length of an encoded scanline + let line_width = (width - 1) / 8 + 1; + + // We'll be writing single bytes, so buffer + let mut line_buffer = Vec::with_capacity(line_width as usize); + + for line in samples.chunks(width as usize) { + for byte_bits in line.chunks(8) { + let mut byte = 0u8; + for i in 0..8 { + // Black pixels are encoded as 1s + if let Some(&v) = byte_bits.get(i) { + if v == V::default() { + byte |= 1u8 << (7 - i) + } + } + } + line_buffer.push(byte) + } + self.0.write_all(line_buffer.as_slice())?; + line_buffer.clear(); + } + + self.0.flush() + } +} + +impl<'a> FlatSamples<'a> { + fn len(&self) -> usize { + match *self { + FlatSamples::U8(arr) => arr.len(), + FlatSamples::U16(arr) => arr.len(), + } + } + + fn all_smaller(&self, max_val: u32) -> bool { + match *self { + FlatSamples::U8(arr) => arr.iter().any(|&val| u32::from(val) > max_val), + FlatSamples::U16(arr) => arr.iter().any(|&val| u32::from(val) > max_val), + } + } + + fn encoding_for(&self, header: &HeaderRecord) -> TupleEncoding<'a> { + match *header { + HeaderRecord::Bitmap(BitmapHeader { + encoding: SampleEncoding::Binary, + width, + .. + }) => TupleEncoding::PbmBits { + samples: *self, + width, + }, + + HeaderRecord::Bitmap(BitmapHeader { + encoding: SampleEncoding::Ascii, + .. + }) => TupleEncoding::Ascii { samples: *self }, + + HeaderRecord::Arbitrary(_) => TupleEncoding::Bytes { samples: *self }, + + HeaderRecord::Graymap(GraymapHeader { + encoding: SampleEncoding::Ascii, + .. + }) + | HeaderRecord::Pixmap(PixmapHeader { + encoding: SampleEncoding::Ascii, + .. + }) => TupleEncoding::Ascii { samples: *self }, + + HeaderRecord::Graymap(GraymapHeader { + encoding: SampleEncoding::Binary, + .. + }) + | HeaderRecord::Pixmap(PixmapHeader { + encoding: SampleEncoding::Binary, + .. + }) => TupleEncoding::Bytes { samples: *self }, + } + } +} + +impl<'a> From<&'a [u8]> for FlatSamples<'a> { + fn from(samples: &'a [u8]) -> Self { + FlatSamples::U8(samples) + } +} + +impl<'a> From<&'a [u16]> for FlatSamples<'a> { + fn from(samples: &'a [u16]) -> Self { + FlatSamples::U16(samples) + } +} + +impl<'a> TupleEncoding<'a> { + fn write_image(&self, writer: &mut dyn Write) -> ImageResult<()> { + match *self { + TupleEncoding::PbmBits { + samples: FlatSamples::U8(samples), + width, + } => SampleWriter(writer) + .write_pbm_bits(samples, width) + .map_err(ImageError::IoError), + TupleEncoding::PbmBits { + samples: FlatSamples::U16(samples), + width, + } => SampleWriter(writer) + .write_pbm_bits(samples, width) + .map_err(ImageError::IoError), + + TupleEncoding::Bytes { + samples: FlatSamples::U8(samples), + } => writer.write_all(samples).map_err(ImageError::IoError), + TupleEncoding::Bytes { + samples: FlatSamples::U16(samples), + } => samples.iter().try_for_each(|&sample| { + writer + .write_u16::<BigEndian>(sample) + .map_err(ImageError::IoError) + }), + + TupleEncoding::Ascii { + samples: FlatSamples::U8(samples), + } => SampleWriter(writer) + .write_samples_ascii(samples.iter()) + .map_err(ImageError::IoError), + TupleEncoding::Ascii { + samples: FlatSamples::U16(samples), + } => SampleWriter(writer) + .write_samples_ascii(samples.iter()) + .map_err(ImageError::IoError), + } + } +} diff --git a/vendor/image/src/codecs/pnm/header.rs b/vendor/image/src/codecs/pnm/header.rs new file mode 100644 index 0000000..443a701 --- /dev/null +++ b/vendor/image/src/codecs/pnm/header.rs @@ -0,0 +1,354 @@ +use std::{fmt, io}; + +/// The kind of encoding used to store sample values +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum SampleEncoding { + /// Samples are unsigned binary integers in big endian + Binary, + + /// Samples are encoded as decimal ascii strings separated by whitespace + Ascii, +} + +/// Denotes the category of the magic number +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum PnmSubtype { + /// Magic numbers P1 and P4 + Bitmap(SampleEncoding), + + /// Magic numbers P2 and P5 + Graymap(SampleEncoding), + + /// Magic numbers P3 and P6 + Pixmap(SampleEncoding), + + /// Magic number P7 + ArbitraryMap, +} + +/// Stores the complete header data of a file. +/// +/// Internally, provides mechanisms for lossless reencoding. After reading a file with the decoder +/// it is possible to recover the header and construct an encoder. Using the encoder on the just +/// loaded image should result in a byte copy of the original file (for single image pnms without +/// additional trailing data). +pub struct PnmHeader { + pub(crate) decoded: HeaderRecord, + pub(crate) encoded: Option<Vec<u8>>, +} + +pub(crate) enum HeaderRecord { + Bitmap(BitmapHeader), + Graymap(GraymapHeader), + Pixmap(PixmapHeader), + Arbitrary(ArbitraryHeader), +} + +/// Header produced by a `pbm` file ("Portable Bit Map") +#[derive(Clone, Copy, Debug)] +pub struct BitmapHeader { + /// Binary or Ascii encoded file + pub encoding: SampleEncoding, + + /// Height of the image file + pub height: u32, + + /// Width of the image file + pub width: u32, +} + +/// Header produced by a `pgm` file ("Portable Gray Map") +#[derive(Clone, Copy, Debug)] +pub struct GraymapHeader { + /// Binary or Ascii encoded file + pub encoding: SampleEncoding, + + /// Height of the image file + pub height: u32, + + /// Width of the image file + pub width: u32, + + /// Maximum sample value within the image + pub maxwhite: u32, +} + +/// Header produced by a `ppm` file ("Portable Pixel Map") +#[derive(Clone, Copy, Debug)] +pub struct PixmapHeader { + /// Binary or Ascii encoded file + pub encoding: SampleEncoding, + + /// Height of the image file + pub height: u32, + + /// Width of the image file + pub width: u32, + + /// Maximum sample value within the image + pub maxval: u32, +} + +/// Header produced by a `pam` file ("Portable Arbitrary Map") +#[derive(Clone, Debug)] +pub struct ArbitraryHeader { + /// Height of the image file + pub height: u32, + + /// Width of the image file + pub width: u32, + + /// Number of color channels + pub depth: u32, + + /// Maximum sample value within the image + pub maxval: u32, + + /// Color interpretation of image pixels + pub tupltype: Option<ArbitraryTuplType>, +} + +/// Standardized tuple type specifiers in the header of a `pam`. +#[derive(Clone, Debug)] +pub enum ArbitraryTuplType { + /// Pixels are either black (0) or white (1) + BlackAndWhite, + + /// Pixels are either black (0) or white (1) and a second alpha channel + BlackAndWhiteAlpha, + + /// Pixels represent the amount of white + Grayscale, + + /// Grayscale with an additional alpha channel + GrayscaleAlpha, + + /// Three channels: Red, Green, Blue + RGB, + + /// Four channels: Red, Green, Blue, Alpha + RGBAlpha, + + /// An image format which is not standardized + Custom(String), +} + +impl ArbitraryTuplType { + pub(crate) fn name(&self) -> &str { + match self { + ArbitraryTuplType::BlackAndWhite => "BLACKANDWHITE", + ArbitraryTuplType::BlackAndWhiteAlpha => "BLACKANDWHITE_ALPHA", + ArbitraryTuplType::Grayscale => "GRAYSCALE", + ArbitraryTuplType::GrayscaleAlpha => "GRAYSCALE_ALPHA", + ArbitraryTuplType::RGB => "RGB", + ArbitraryTuplType::RGBAlpha => "RGB_ALPHA", + ArbitraryTuplType::Custom(custom) => custom, + } + } +} + +impl PnmSubtype { + /// Get the two magic constant bytes corresponding to this format subtype. + pub fn magic_constant(self) -> &'static [u8; 2] { + match self { + PnmSubtype::Bitmap(SampleEncoding::Ascii) => b"P1", + PnmSubtype::Graymap(SampleEncoding::Ascii) => b"P2", + PnmSubtype::Pixmap(SampleEncoding::Ascii) => b"P3", + PnmSubtype::Bitmap(SampleEncoding::Binary) => b"P4", + PnmSubtype::Graymap(SampleEncoding::Binary) => b"P5", + PnmSubtype::Pixmap(SampleEncoding::Binary) => b"P6", + PnmSubtype::ArbitraryMap => b"P7", + } + } + + /// Whether samples are stored as binary or as decimal ascii + pub fn sample_encoding(self) -> SampleEncoding { + match self { + PnmSubtype::ArbitraryMap => SampleEncoding::Binary, + PnmSubtype::Bitmap(enc) => enc, + PnmSubtype::Graymap(enc) => enc, + PnmSubtype::Pixmap(enc) => enc, + } + } +} + +impl PnmHeader { + /// Retrieve the format subtype from which the header was created. + pub fn subtype(&self) -> PnmSubtype { + match self.decoded { + HeaderRecord::Bitmap(BitmapHeader { encoding, .. }) => PnmSubtype::Bitmap(encoding), + HeaderRecord::Graymap(GraymapHeader { encoding, .. }) => PnmSubtype::Graymap(encoding), + HeaderRecord::Pixmap(PixmapHeader { encoding, .. }) => PnmSubtype::Pixmap(encoding), + HeaderRecord::Arbitrary(ArbitraryHeader { .. }) => PnmSubtype::ArbitraryMap, + } + } + + /// The width of the image this header is for. + pub fn width(&self) -> u32 { + match self.decoded { + HeaderRecord::Bitmap(BitmapHeader { width, .. }) => width, + HeaderRecord::Graymap(GraymapHeader { width, .. }) => width, + HeaderRecord::Pixmap(PixmapHeader { width, .. }) => width, + HeaderRecord::Arbitrary(ArbitraryHeader { width, .. }) => width, + } + } + + /// The height of the image this header is for. + pub fn height(&self) -> u32 { + match self.decoded { + HeaderRecord::Bitmap(BitmapHeader { height, .. }) => height, + HeaderRecord::Graymap(GraymapHeader { height, .. }) => height, + HeaderRecord::Pixmap(PixmapHeader { height, .. }) => height, + HeaderRecord::Arbitrary(ArbitraryHeader { height, .. }) => height, + } + } + + /// The biggest value a sample can have. In other words, the colour resolution. + pub fn maximal_sample(&self) -> u32 { + match self.decoded { + HeaderRecord::Bitmap(BitmapHeader { .. }) => 1, + HeaderRecord::Graymap(GraymapHeader { maxwhite, .. }) => maxwhite, + HeaderRecord::Pixmap(PixmapHeader { maxval, .. }) => maxval, + HeaderRecord::Arbitrary(ArbitraryHeader { maxval, .. }) => maxval, + } + } + + /// Retrieve the underlying bitmap header if any + pub fn as_bitmap(&self) -> Option<&BitmapHeader> { + match self.decoded { + HeaderRecord::Bitmap(ref bitmap) => Some(bitmap), + _ => None, + } + } + + /// Retrieve the underlying graymap header if any + pub fn as_graymap(&self) -> Option<&GraymapHeader> { + match self.decoded { + HeaderRecord::Graymap(ref graymap) => Some(graymap), + _ => None, + } + } + + /// Retrieve the underlying pixmap header if any + pub fn as_pixmap(&self) -> Option<&PixmapHeader> { + match self.decoded { + HeaderRecord::Pixmap(ref pixmap) => Some(pixmap), + _ => None, + } + } + + /// Retrieve the underlying arbitrary header if any + pub fn as_arbitrary(&self) -> Option<&ArbitraryHeader> { + match self.decoded { + HeaderRecord::Arbitrary(ref arbitrary) => Some(arbitrary), + _ => None, + } + } + + /// Write the header back into a binary stream + pub fn write(&self, writer: &mut dyn io::Write) -> io::Result<()> { + writer.write_all(self.subtype().magic_constant())?; + match *self { + PnmHeader { + encoded: Some(ref content), + .. + } => writer.write_all(content), + PnmHeader { + decoded: + HeaderRecord::Bitmap(BitmapHeader { + encoding: _encoding, + width, + height, + }), + .. + } => writeln!(writer, "\n{} {}", width, height), + PnmHeader { + decoded: + HeaderRecord::Graymap(GraymapHeader { + encoding: _encoding, + width, + height, + maxwhite, + }), + .. + } => writeln!(writer, "\n{} {} {}", width, height, maxwhite), + PnmHeader { + decoded: + HeaderRecord::Pixmap(PixmapHeader { + encoding: _encoding, + width, + height, + maxval, + }), + .. + } => writeln!(writer, "\n{} {} {}", width, height, maxval), + PnmHeader { + decoded: + HeaderRecord::Arbitrary(ArbitraryHeader { + width, + height, + depth, + maxval, + ref tupltype, + }), + .. + } => { + struct TupltypeWriter<'a>(&'a Option<ArbitraryTuplType>); + impl<'a> fmt::Display for TupltypeWriter<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.0 { + Some(tt) => writeln!(f, "TUPLTYPE {}", tt.name()), + None => Ok(()), + } + } + } + + writeln!( + writer, + "\nWIDTH {}\nHEIGHT {}\nDEPTH {}\nMAXVAL {}\n{}ENDHDR", + width, + height, + depth, + maxval, + TupltypeWriter(tupltype) + ) + } + } + } +} + +impl From<BitmapHeader> for PnmHeader { + fn from(header: BitmapHeader) -> Self { + PnmHeader { + decoded: HeaderRecord::Bitmap(header), + encoded: None, + } + } +} + +impl From<GraymapHeader> for PnmHeader { + fn from(header: GraymapHeader) -> Self { + PnmHeader { + decoded: HeaderRecord::Graymap(header), + encoded: None, + } + } +} + +impl From<PixmapHeader> for PnmHeader { + fn from(header: PixmapHeader) -> Self { + PnmHeader { + decoded: HeaderRecord::Pixmap(header), + encoded: None, + } + } +} + +impl From<ArbitraryHeader> for PnmHeader { + fn from(header: ArbitraryHeader) -> Self { + PnmHeader { + decoded: HeaderRecord::Arbitrary(header), + encoded: None, + } + } +} diff --git a/vendor/image/src/codecs/pnm/mod.rs b/vendor/image/src/codecs/pnm/mod.rs new file mode 100644 index 0000000..de8612d --- /dev/null +++ b/vendor/image/src/codecs/pnm/mod.rs @@ -0,0 +1,184 @@ +//! Decoding of netpbm image formats (pbm, pgm, ppm and pam). +//! +//! The formats pbm, pgm and ppm are fully supported. The pam decoder recognizes the tuple types +//! `BLACKANDWHITE`, `GRAYSCALE` and `RGB` and explicitly recognizes but rejects their `_ALPHA` +//! variants for now as alpha color types are unsupported. +use self::autobreak::AutoBreak; +pub use self::decoder::PnmDecoder; +pub use self::encoder::PnmEncoder; +use self::header::HeaderRecord; +pub use self::header::{ + ArbitraryHeader, ArbitraryTuplType, BitmapHeader, GraymapHeader, PixmapHeader, +}; +pub use self::header::{PnmHeader, PnmSubtype, SampleEncoding}; + +mod autobreak; +mod decoder; +mod encoder; +mod header; + +#[cfg(test)] +mod tests { + use super::*; + use crate::color::ColorType; + use crate::image::ImageDecoder; + use byteorder::{ByteOrder, NativeEndian}; + + fn execute_roundtrip_default(buffer: &[u8], width: u32, height: u32, color: ColorType) { + let mut encoded_buffer = Vec::new(); + + { + let mut encoder = PnmEncoder::new(&mut encoded_buffer); + encoder + .encode(buffer, width, height, color) + .expect("Failed to encode the image buffer"); + } + + let (header, loaded_color, loaded_image) = { + let decoder = PnmDecoder::new(&encoded_buffer[..]).unwrap(); + let color_type = decoder.color_type(); + let mut image = vec![0; decoder.total_bytes() as usize]; + decoder + .read_image(&mut image) + .expect("Failed to decode the image"); + let (_, header) = PnmDecoder::new(&encoded_buffer[..]).unwrap().into_inner(); + (header, color_type, image) + }; + + assert_eq!(header.width(), width); + assert_eq!(header.height(), height); + assert_eq!(loaded_color, color); + assert_eq!(loaded_image.as_slice(), buffer); + } + + fn execute_roundtrip_with_subtype( + buffer: &[u8], + width: u32, + height: u32, + color: ColorType, + subtype: PnmSubtype, + ) { + let mut encoded_buffer = Vec::new(); + + { + let mut encoder = PnmEncoder::new(&mut encoded_buffer).with_subtype(subtype); + encoder + .encode(buffer, width, height, color) + .expect("Failed to encode the image buffer"); + } + + let (header, loaded_color, loaded_image) = { + let decoder = PnmDecoder::new(&encoded_buffer[..]).unwrap(); + let color_type = decoder.color_type(); + let mut image = vec![0; decoder.total_bytes() as usize]; + decoder + .read_image(&mut image) + .expect("Failed to decode the image"); + let (_, header) = PnmDecoder::new(&encoded_buffer[..]).unwrap().into_inner(); + (header, color_type, image) + }; + + assert_eq!(header.width(), width); + assert_eq!(header.height(), height); + assert_eq!(header.subtype(), subtype); + assert_eq!(loaded_color, color); + assert_eq!(loaded_image.as_slice(), buffer); + } + + fn execute_roundtrip_u16(buffer: &[u16], width: u32, height: u32, color: ColorType) { + let mut encoded_buffer = Vec::new(); + + { + let mut encoder = PnmEncoder::new(&mut encoded_buffer); + encoder + .encode(buffer, width, height, color) + .expect("Failed to encode the image buffer"); + } + + let (header, loaded_color, loaded_image) = { + let decoder = PnmDecoder::new(&encoded_buffer[..]).unwrap(); + let color_type = decoder.color_type(); + let mut image = vec![0; decoder.total_bytes() as usize]; + decoder + .read_image(&mut image) + .expect("Failed to decode the image"); + let (_, header) = PnmDecoder::new(&encoded_buffer[..]).unwrap().into_inner(); + (header, color_type, image) + }; + + let mut buffer_u8 = vec![0; buffer.len() * 2]; + NativeEndian::write_u16_into(buffer, &mut buffer_u8[..]); + + assert_eq!(header.width(), width); + assert_eq!(header.height(), height); + assert_eq!(loaded_color, color); + assert_eq!(loaded_image, buffer_u8); + } + + #[test] + fn roundtrip_gray() { + #[rustfmt::skip] + let buf: [u8; 16] = [ + 0, 0, 0, 255, + 255, 255, 255, 255, + 255, 0, 255, 0, + 255, 0, 0, 0, + ]; + + execute_roundtrip_default(&buf, 4, 4, ColorType::L8); + execute_roundtrip_with_subtype(&buf, 4, 4, ColorType::L8, PnmSubtype::ArbitraryMap); + execute_roundtrip_with_subtype( + &buf, + 4, + 4, + ColorType::L8, + PnmSubtype::Graymap(SampleEncoding::Ascii), + ); + execute_roundtrip_with_subtype( + &buf, + 4, + 4, + ColorType::L8, + PnmSubtype::Graymap(SampleEncoding::Binary), + ); + } + + #[test] + fn roundtrip_rgb() { + #[rustfmt::skip] + let buf: [u8; 27] = [ + 0, 0, 0, + 0, 0, 255, + 0, 255, 0, + 0, 255, 255, + 255, 0, 0, + 255, 0, 255, + 255, 255, 0, + 255, 255, 255, + 255, 255, 255, + ]; + execute_roundtrip_default(&buf, 3, 3, ColorType::Rgb8); + execute_roundtrip_with_subtype(&buf, 3, 3, ColorType::Rgb8, PnmSubtype::ArbitraryMap); + execute_roundtrip_with_subtype( + &buf, + 3, + 3, + ColorType::Rgb8, + PnmSubtype::Pixmap(SampleEncoding::Binary), + ); + execute_roundtrip_with_subtype( + &buf, + 3, + 3, + ColorType::Rgb8, + PnmSubtype::Pixmap(SampleEncoding::Ascii), + ); + } + + #[test] + fn roundtrip_u16() { + let buf: [u16; 6] = [0, 1, 0xFFFF, 0x1234, 0x3412, 0xBEAF]; + + execute_roundtrip_u16(&buf, 6, 1, ColorType::L16); + } +} |