summaryrefslogtreecommitdiffstats
path: root/third_party/rust/image/src/pnm
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/image/src/pnm')
-rw-r--r--third_party/rust/image/src/pnm/autobreak.rs124
-rw-r--r--third_party/rust/image/src/pnm/decoder.rs1104
-rw-r--r--third_party/rust/image/src/pnm/encoder.rs653
-rw-r--r--third_party/rust/image/src/pnm/header.rs348
-rw-r--r--third_party/rust/image/src/pnm/mod.rs149
5 files changed, 2378 insertions, 0 deletions
diff --git a/third_party/rust/image/src/pnm/autobreak.rs b/third_party/rust/image/src/pnm/autobreak.rs
new file mode 100644
index 0000000000..cea2cd8f2b
--- /dev/null
+++ b/third_party/rust/image/src/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/third_party/rust/image/src/pnm/decoder.rs b/third_party/rust/image/src/pnm/decoder.rs
new file mode 100644
index 0000000000..85c8e43787
--- /dev/null
+++ b/third_party/rust/image/src/pnm/decoder.rs
@@ -0,0 +1,1104 @@
+use std::convert::TryFrom;
+use std::io::{self, BufRead, BufReader, Cursor, Read};
+use std::str::{self, FromStr};
+use std::fmt::Display;
+use std::marker::PhantomData;
+use std::mem;
+
+use super::{ArbitraryHeader, ArbitraryTuplType, BitmapHeader, GraymapHeader, PixmapHeader};
+use super::{HeaderRecord, PNMHeader, PNMSubtype, SampleEncoding};
+use crate::color::{ColorType, ExtendedColorType};
+use crate::error::{ImageError, ImageResult};
+use crate::image::{self, ImageDecoder};
+use crate::utils;
+
+use byteorder::{BigEndian, ByteOrder, NativeEndian};
+
+/// 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>;
+
+ /// It is guaranteed that `bytes.len() == bytelen(width, height, samples)`
+ fn from_bytes(bytes: &[u8], width: u32, height: u32, samples: u32)
+ -> ImageResult<Vec<u8>>;
+
+ fn from_ascii(reader: &mut dyn Read, width: u32, height: u32, samples: u32)
+ -> ImageResult<Vec<u8>>;
+}
+
+struct U8;
+struct U16;
+struct PbmBit;
+struct BWBit;
+
+trait DecodableImageHeader {
+ fn tuple_type(&self) -> ImageResult<TupleType>;
+}
+
+/// PNM decoder
+pub struct PnmDecoder<R> {
+ reader: BufReader<R>,
+ header: PNMHeader,
+ tuple: TupleType,
+}
+
+impl<R: Read> PnmDecoder<R> {
+ /// Create a new decoder that decodes from the stream ```read```
+ pub fn new(read: R) -> ImageResult<PnmDecoder<R>> {
+ let mut buf = BufReader::new(read);
+ let magic = buf.read_magic_constant()?;
+ if magic[0] != b'P' {
+ return Err(ImageError::FormatError(
+ format!("Expected magic constant for pnm, P1 through P7 instead of {:?}", magic),
+ ));
+ }
+
+ let subtype = match magic[1] {
+ b'1' => PNMSubtype::Bitmap(SampleEncoding::Ascii),
+ b'2' => PNMSubtype::Graymap(SampleEncoding::Ascii),
+ b'3' => PNMSubtype::Pixmap(SampleEncoding::Ascii),
+ b'4' => PNMSubtype::Bitmap(SampleEncoding::Binary),
+ b'5' => PNMSubtype::Graymap(SampleEncoding::Binary),
+ b'6' => PNMSubtype::Pixmap(SampleEncoding::Binary),
+ b'7' => PNMSubtype::ArbitraryMap,
+ _ => {
+ return Err(ImageError::FormatError(
+ format!("Expected magic constant for pnm, P1 through P7 instead of {:?}", magic),
+ ));
+ }
+ };
+
+ match subtype {
+ PNMSubtype::Bitmap(enc) => PnmDecoder::read_bitmap_header(buf, enc),
+ PNMSubtype::Graymap(enc) => PnmDecoder::read_graymap_header(buf, enc),
+ PNMSubtype::Pixmap(enc) => PnmDecoder::read_pixmap_header(buf, enc),
+ PNMSubtype::ArbitraryMap => PnmDecoder::read_arbitrary_header(buf),
+ }
+ }
+
+ /// Extract the reader and header after an image has been read.
+ pub fn into_inner(self) -> (R, PNMHeader) {
+ (self.reader.into_inner(), self.header)
+ }
+
+ fn read_bitmap_header(
+ mut reader: BufReader<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: BufReader<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: BufReader<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: BufReader<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)
+ .map_err(ImageError::IoError)?;
+ 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(|ref 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(ImageError::FormatError(
+ format!("Non ascii character {} in header", byte),
+ ));
+ },
+ 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)
+ .map_err(ImageError::IoError)?;
+ Ok(buffer)
+ }
+
+ fn read_next_u32(&mut self) -> ImageResult<u32> {
+ let s = self.read_next_string()?;
+ s.parse::<u32>()
+ .map_err(|err| ImageError::FormatError(
+ format!("Error parsing number {} in preamble: {}", s, err)
+ ))
+ }
+
+ 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> {
+ 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(ImageError::FormatError(
+ format!("Expected newline after P7 magic instead of {}", c),
+ ))
+ }
+ }
+
+ 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).map_err(ImageError::IoError)?;
+ if len == 0 {
+ return Err(ImageError::FormatError(
+ "Unexpected end of pnm header".to_string(),
+ ))
+ }
+ if line.as_bytes()[0] == b'#' {
+ continue;
+ }
+ if !line.is_ascii() {
+ return Err(ImageError::FormatError(
+ "Only ascii characters allowed in pam header".to_string(),
+ ));
+ }
+ #[allow(deprecated)]
+ let (identifier, rest) = line.trim_left()
+ .split_at(line.find(char::is_whitespace).unwrap_or_else(|| line.len()));
+ match identifier {
+ "ENDHDR" => break,
+ "HEIGHT" => if height.is_some() {
+ return Err(ImageError::FormatError("Duplicate HEIGHT line".to_string()));
+ } else {
+ let h = rest.trim()
+ .parse::<u32>()
+ .map_err(|err| ImageError::FormatError(
+ format!("Invalid height {}: {}", rest, err)
+ ))?;
+ height = Some(h);
+ },
+ "WIDTH" => if width.is_some() {
+ return Err(ImageError::FormatError("Duplicate WIDTH line".to_string()));
+ } else {
+ let w = rest.trim()
+ .parse::<u32>()
+ .map_err(|err| ImageError::FormatError(
+ format!("Invalid width {}: {}", rest, err)
+ ))?;
+ width = Some(w);
+ },
+ "DEPTH" => if depth.is_some() {
+ return Err(ImageError::FormatError("Duplicate DEPTH line".to_string()));
+ } else {
+ let d = rest.trim()
+ .parse::<u32>()
+ .map_err(|err| ImageError::FormatError(
+ format!("Invalid depth {}: {}", rest, err)
+ ))?;
+ depth = Some(d);
+ },
+ "MAXVAL" => if maxval.is_some() {
+ return Err(ImageError::FormatError("Duplicate MAXVAL line".to_string()));
+ } else {
+ let m = rest.trim()
+ .parse::<u32>()
+ .map_err(|err| ImageError::FormatError(
+ format!("Invalid maxval {}: {}", rest, err)
+ ))?;
+ maxval = Some(m);
+ },
+ "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(ImageError::FormatError("Unknown header line".to_string())),
+ }
+ }
+
+ let (h, w, d, m) = match (height, width, depth, maxval) {
+ (None, _, _, _) => {
+ return Err(ImageError::FormatError(
+ "Expected one HEIGHT line".to_string(),
+ ))
+ }
+ (_, None, _, _) => {
+ return Err(ImageError::FormatError(
+ "Expected one WIDTH line".to_string(),
+ ))
+ }
+ (_, _, None, _) => {
+ return Err(ImageError::FormatError(
+ "Expected one DEPTH line".to_string(),
+ ))
+ }
+ (_, _, _, None) => {
+ return Err(ImageError::FormatError(
+ "Expected one MAXVAL line".to_string(),
+ ))
+ }
+ (Some(h), Some(w), Some(d), Some(m)) => (h, w, d, m),
+ };
+
+ 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: Read> HeaderReader for BufReader<R> {}
+
+/// 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()));
+ buf.copy_from_slice(&match self.tuple {
+ TupleType::PbmBit => self.read_samples::<PbmBit>(1),
+ TupleType::BWBit => self.read_samples::<BWBit>(1),
+ TupleType::RGBU8 => self.read_samples::<U8>(3),
+ TupleType::RGBU16 => self.read_samples::<U16>(3),
+ TupleType::GrayU8 => self.read_samples::<U8>(1),
+ TupleType::GrayU16 => self.read_samples::<U16>(1),
+ }?);
+ Ok(())
+ }
+}
+
+fn err_input_is_too_short() -> ImageError {
+ return ImageError::FormatError(
+ "Not enough data was provided to the Decoder to decode the image".into()
+ )
+}
+
+impl<R: Read> PnmDecoder<R> {
+ fn read_samples<S: Sample>(&mut self, components: u32) -> ImageResult<Vec<u8>> {
+ 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(err_input_is_too_short());
+ }
+
+ let samples = S::from_bytes(&bytes, width, height, components)?;
+ Ok(samples)
+ }
+ SampleEncoding::Ascii => {
+ let samples = self.read_ascii::<S>(components)?;
+ Ok(samples)
+ }
+ }
+ }
+
+ fn read_ascii<Basic: Sample>(&mut self, components: u32) -> ImageResult<Vec<u8>> {
+ Basic::from_ascii(&mut self.reader, self.header.width(), self.header.height(), components)
+ }
+
+ /// 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>(reader: &mut dyn Read) -> ImageResult<T>
+ where T::Err: Display
+{
+ let is_separator = |v: &u8| match *v {
+ b'\t' | b'\n' | b'\x0b' | b'\x0c' | b'\r' | b' ' => true,
+ _ => false,
+ };
+
+ 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(ImageError::FormatError(
+ "Non ascii character where sample value was expected".to_string(),
+ ));
+ }
+
+ let string = str::from_utf8(&token)
+ // We checked the precondition ourselves a few lines before, `token.is_ascii()`.
+ .unwrap_or_else(|_| unreachable!("Only ascii characters should be decoded"));
+
+ string
+ .parse()
+ .map_err(|err| ImageError::FormatError(format!("Error parsing {} as a sample: {}", string, err)))
+}
+
+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],
+ width: u32,
+ height: u32,
+ samples: u32,
+ ) -> ImageResult<Vec<u8>> {
+ assert_eq!(bytes.len(), Self::bytelen(width, height, samples).unwrap());
+ Ok(bytes.to_vec())
+ }
+
+ fn from_ascii(
+ reader: &mut dyn Read,
+ width: u32,
+ height: u32,
+ samples: u32,
+ ) -> ImageResult<Vec<u8>> {
+ (0..width*height*samples)
+ .map(|_| read_separated_ascii(reader))
+ .collect()
+ }
+}
+
+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],
+ width: u32,
+ height: u32,
+ samples: u32,
+ ) -> ImageResult<Vec<u8>> {
+ assert_eq!(bytes.len(), Self::bytelen(width, height, samples).unwrap());
+
+ let mut buffer = bytes.to_vec();
+ for chunk in buffer.chunks_mut(2) {
+ let v = BigEndian::read_u16(chunk);
+ NativeEndian::write_u16(chunk, v);
+ }
+ Ok(buffer)
+ }
+
+ fn from_ascii(
+ reader: &mut dyn Read,
+ width: u32,
+ height: u32,
+ samples: u32,
+ ) -> ImageResult<Vec<u8>> {
+ let mut buffer = vec![0; (width * height * samples * 2) as usize];
+ for i in 0..(width*height*samples) as usize {
+ let v = read_separated_ascii::<u16>(reader)?;
+ NativeEndian::write_u16(&mut buffer[2*i..][..2], v);
+ }
+ Ok(buffer)
+ }
+}
+
+// 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],
+ width: u32,
+ height: u32,
+ samples: u32,
+ ) -> ImageResult<Vec<u8>> {
+ assert_eq!(bytes.len(), Self::bytelen(width, height, samples).unwrap());
+
+ let mut expanded = utils::expand_bits(1, width * samples, bytes);
+ for b in expanded.iter_mut() {
+ *b = !*b;
+ }
+ Ok(expanded)
+ }
+
+ fn from_ascii(
+ reader: &mut dyn Read,
+ width: u32,
+ height: u32,
+ samples: u32,
+ ) -> ImageResult<Vec<u8>> {
+ let count = (width*height*samples) as usize;
+ let raw_samples = reader.bytes()
+ .filter_map(|ascii| match ascii {
+ Ok(b'0') => Some(Ok(1)),
+ Ok(b'1') => Some(Ok(0)),
+ Err(err) => Some(Err(ImageError::IoError(err))),
+ Ok(b'\t')
+ | Ok(b'\n')
+ | Ok(b'\x0b')
+ | Ok(b'\x0c')
+ | Ok(b'\r')
+ | Ok(b' ') => None,
+ Ok(c) => Some(Err(ImageError::FormatError(
+ format!("Unexpected character {} within sample raster", c),
+ ))),
+ })
+ .take(count)
+ .collect::<ImageResult<Vec<u8>>>()?;
+
+ if raw_samples.len() < count {
+ return Err(err_input_is_too_short())
+ }
+
+ Ok(raw_samples)
+ }
+}
+
+// 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],
+ width: u32,
+ height: u32,
+ samples: u32,
+ ) -> ImageResult<Vec<u8>> {
+ assert_eq!(bytes.len(), Self::bytelen(width, height, samples).unwrap());
+
+ let values = U8::from_bytes(bytes, width, height, samples)?;
+ if let Some(val) = values.iter().find(|&val| *val > 1) {
+ return Err(ImageError::FormatError(
+ format!("Sample value {} outside of bounds", val),
+ ));
+ };
+ Ok(values)
+ }
+
+ fn from_ascii(
+ _reader: &mut dyn Read,
+ _width: u32,
+ _height: u32,
+ _samples: u32,
+ ) -> ImageResult<Vec<u8>> {
+ 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(ImageError::FormatError(
+ "Image maxval is not less or equal to 65535".to_string(),
+ )),
+ }
+ }
+}
+
+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(ImageError::FormatError(
+ "Image maxval is not less or equal to 65535".to_string(),
+ )),
+ }
+ }
+}
+
+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::UnsupportedColor(ExtendedColorType::La8)),
+ None if self.depth == 3 => Ok(TupleType::RGBU8),
+ None if self.depth == 4 => Err(ImageError::UnsupportedColor(ExtendedColorType::Rgba8)),
+
+ Some(ArbitraryTuplType::BlackAndWhite) if self.maxval == 1 && self.depth == 1 => {
+ Ok(TupleType::BWBit)
+ }
+ Some(ArbitraryTuplType::BlackAndWhite) => Err(ImageError::FormatError(
+ "Invalid depth or maxval for tuple type BLACKANDWHITE".to_string(),
+ )),
+
+ 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(ImageError::FormatError(
+ "Invalid depth or maxval for tuple type GRAYSCALE".to_string(),
+ )),
+
+ 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(ImageError::FormatError(
+ "Invalid depth for tuple type RGB".to_string(),
+ )),
+
+ Some(ArbitraryTuplType::BlackAndWhiteAlpha) => Err(ImageError::FormatError(
+ "Unsupported color type: BlackAndWhiteAlpha".to_string()
+ )),
+ Some(ArbitraryTuplType::GrayscaleAlpha) => {
+ Err(ImageError::UnsupportedColor(ExtendedColorType::La8))
+ }
+ Some(ArbitraryTuplType::RGBAlpha) => {
+ Err(ImageError::UnsupportedColor(ExtendedColorType::Rgba8))
+ }
+ _ => Err(ImageError::FormatError(
+ "Tuple type not recognized".to_string(),
+ )),
+ }
+ }
+}
+#[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 inifite loop.
+ #[test]
+ fn pbm_binary_ascii_termination() {
+ use std::io::{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 = 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![1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 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![1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 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"),
+ }
+ }
+}
diff --git a/third_party/rust/image/src/pnm/encoder.rs b/third_party/rust/image/src/pnm/encoder.rs
new file mode 100644
index 0000000000..5e9d2ed769
--- /dev/null
+++ b/third_party/rust/image/src/pnm/encoder.rs
@@ -0,0 +1,653 @@
+//! 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};
+use crate::image::ImageEncoder;
+
+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::UnsupportedColor(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,
+ },
+ (_, _) => {
+ // FIXME https://github.com/image-rs/image/issues/921
+ return Err(ImageError::IoError(io::Error::new(
+ io::ErrorKind::InvalidInput,
+ "Color type can not be represented in the chosen format",
+ )))
+ }
+ };
+
+ 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;
+ match Some(components)
+ .and_then(|v| v.checked_mul(uwidth))
+ .and_then(|v| v.checked_mul(uheight))
+ {
+ None => Err(ImageError::DimensionError),
+ Some(v) if v == image.len() => Ok(CheckedImageBuffer {
+ _image: image,
+ _width: width,
+ _height: height,
+ _color: color,
+ }),
+ Some(_) => Err(ImageError::IoError(io::Error::new(
+ io::ErrorKind::InvalidInput,
+ &"Image buffer does not correspond to size and colour".to_string()[..],
+ ))),
+ }
+ }
+}
+
+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 {
+ return Err(ImageError::IoError(io::Error::new(
+ io::ErrorKind::InvalidInput,
+ "Chosen header does not match Image dimensions",
+ )));
+ }
+
+ 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 comination 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::IoError(io::Error::new(
+ io::ErrorKind::InvalidInput,
+ "PBM format only support luma color types",
+ )))
+ }
+ },
+ PNMHeader {
+ decoded: HeaderRecord::Graymap(_),
+ ..
+ } => match color {
+ ExtendedColorType::L1 | ExtendedColorType::L8 | ExtendedColorType::L16 => (),
+ _ => {
+ return Err(ImageError::IoError(io::Error::new(
+ io::ErrorKind::InvalidInput,
+ "PGM format only support luma color types",
+ )))
+ }
+ },
+ PNMHeader {
+ decoded: HeaderRecord::Pixmap(_),
+ ..
+ } => match color {
+ ExtendedColorType::Rgb8 => (),
+ _ => {
+ return Err(ImageError::IoError(io::Error::new(
+ io::ErrorKind::InvalidInput,
+ "PPM format only support ExtendedColorType::Rgb8",
+ )))
+ }
+ },
+ 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::IoError(io::Error::new(
+ io::ErrorKind::InvalidInput,
+ format!("Depth mismatch: header {} vs. color {}", depth, components),
+ )))
+ }
+ _ => {
+ return Err(ImageError::IoError(io::Error::new(
+ io::ErrorKind::InvalidInput,
+ "Invalid color type for selected PAM color type",
+ )))
+ }
+ },
+ }
+
+ 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,
+ ExtendedColorType::__NonExhaustive(marker) => match marker._private {},
+ _ => {
+ return Err(ImageError::IoError(io::Error::new(
+ io::ErrorKind::InvalidInput,
+ "Unsupported target color type",
+ )))
+ }
+ };
+
+ // 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) {
+ // FIXME https://github.com/image-rs/image/issues/921, No ImageError variant seems
+ // appropriate in this situation UnsupportedHeaderFormat maybe?
+ return Err(ImageError::IoError(io::Error::new(
+ io::ErrorKind::InvalidInput,
+ "Sample value greater than allowed for chosen header",
+ )));
+ }
+
+ 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()
+ .map(|&sample| {
+ writer
+ .write_u16::<BigEndian>(sample)
+ .map_err(ImageError::IoError)
+ })
+ .collect(),
+
+ 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/third_party/rust/image/src/pnm/header.rs b/third_party/rust/image/src/pnm/header.rs
new file mode 100644
index 0000000000..927d8fa64c
--- /dev/null
+++ b/third_party/rust/image/src/pnm/header.rs
@@ -0,0 +1,348 @@
+use std::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 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,
+ }),
+ ..
+ } => {
+ #[allow(unused_assignments)]
+ // Declared here so its lifetime exceeds the matching. This is a trivial
+ // constructor, no allocation takes place and in the custom case we must allocate
+ // regardless due to borrow. Still, the warnings checker does pick this up :/
+ // Could use std::borrow::Cow instead but that really doesn't achieve anything but
+ // increasing type complexity.
+ let mut custom_fallback = String::new();
+
+ let tupltype = match *tupltype {
+ None => "",
+ Some(ArbitraryTuplType::BlackAndWhite) => "TUPLTYPE BLACKANDWHITE\n",
+ Some(ArbitraryTuplType::BlackAndWhiteAlpha) => "TUPLTYPE BLACKANDWHITE_ALPHA\n",
+ Some(ArbitraryTuplType::Grayscale) => "TUPLTYPE GRAYSCALE\n",
+ Some(ArbitraryTuplType::GrayscaleAlpha) => "TUPLTYPE GRAYSCALE_ALPHA\n",
+ Some(ArbitraryTuplType::RGB) => "TUPLTYPE RGB\n",
+ Some(ArbitraryTuplType::RGBAlpha) => "TUPLTYPE RGB_ALPHA\n",
+ Some(ArbitraryTuplType::Custom(ref custom)) => {
+ custom_fallback = format!("TUPLTYPE {}", custom);
+ &custom_fallback
+ }
+ };
+
+ writeln!(
+ writer,
+ "\nWIDTH {}\nHEIGHT {}\nDEPTH {}\nMAXVAL {}\n{}ENDHDR",
+ width, height, depth, maxval, 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/third_party/rust/image/src/pnm/mod.rs b/third_party/rust/image/src/pnm/mod.rs
new file mode 100644
index 0000000000..f1c568f13a
--- /dev/null
+++ b/third_party/rust/image/src/pnm/mod.rs
@@ -0,0 +1,149 @@
+//! 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 explicitely 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 byteorder::{ByteOrder, NativeEndian};
+ use crate::color::ColorType;
+ use crate::image::ImageDecoder;
+
+ 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_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);
+ }
+}