diff options
Diffstat (limited to 'vendor/image/src/codecs/pnm/encoder.rs')
-rw-r--r-- | vendor/image/src/codecs/pnm/encoder.rs | 673 |
1 files changed, 0 insertions, 673 deletions
diff --git a/vendor/image/src/codecs/pnm/encoder.rs b/vendor/image/src/codecs/pnm/encoder.rs deleted file mode 100644 index 9f823d0..0000000 --- a/vendor/image/src/codecs/pnm/encoder.rs +++ /dev/null @@ -1,673 +0,0 @@ -//! 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), - } - } -} |