diff options
Diffstat (limited to 'vendor/image/src/codecs/tga/encoder.rs')
-rw-r--r-- | vendor/image/src/codecs/tga/encoder.rs | 215 |
1 files changed, 215 insertions, 0 deletions
diff --git a/vendor/image/src/codecs/tga/encoder.rs b/vendor/image/src/codecs/tga/encoder.rs new file mode 100644 index 0000000..cf34984 --- /dev/null +++ b/vendor/image/src/codecs/tga/encoder.rs @@ -0,0 +1,215 @@ +use super::header::Header; +use crate::{error::EncodingError, ColorType, ImageEncoder, ImageError, ImageFormat, ImageResult}; +use std::{convert::TryFrom, error, fmt, io::Write}; + +/// Errors that can occur during encoding and saving of a TGA image. +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] +enum EncoderError { + /// Invalid TGA width. + WidthInvalid(u32), + + /// Invalid TGA height. + HeightInvalid(u32), +} + +impl fmt::Display for EncoderError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + EncoderError::WidthInvalid(s) => f.write_fmt(format_args!("Invalid TGA width: {}", s)), + EncoderError::HeightInvalid(s) => { + f.write_fmt(format_args!("Invalid TGA height: {}", s)) + } + } + } +} + +impl From<EncoderError> for ImageError { + fn from(e: EncoderError) -> ImageError { + ImageError::Encoding(EncodingError::new(ImageFormat::Tga.into(), e)) + } +} + +impl error::Error for EncoderError {} + +/// TGA encoder. +pub struct TgaEncoder<W: Write> { + writer: W, +} + +impl<W: Write> TgaEncoder<W> { + /// Create a new encoder that writes its output to ```w```. + pub fn new(w: W) -> TgaEncoder<W> { + TgaEncoder { writer: w } + } + + /// Encodes the image ```buf``` that has dimensions ```width``` + /// and ```height``` and ```ColorType``` ```color_type```. + /// + /// The dimensions of the image must be between 0 and 65535 (inclusive) or + /// an error will be returned. + pub fn encode( + mut self, + buf: &[u8], + width: u32, + height: u32, + color_type: ColorType, + ) -> ImageResult<()> { + // Validate dimensions. + let width = u16::try_from(width) + .map_err(|_| ImageError::from(EncoderError::WidthInvalid(width)))?; + + let height = u16::try_from(height) + .map_err(|_| ImageError::from(EncoderError::HeightInvalid(height)))?; + + // Write out TGA header. + let header = Header::from_pixel_info(color_type, width, height)?; + header.write_to(&mut self.writer)?; + + // Write out Bgr(a)8 or L(a)8 image data. + match color_type { + ColorType::Rgb8 | ColorType::Rgba8 => { + let mut image = Vec::from(buf); + + for chunk in image.chunks_mut(usize::from(color_type.bytes_per_pixel())) { + chunk.swap(0, 2); + } + + self.writer.write_all(&image)?; + } + _ => { + self.writer.write_all(buf)?; + } + } + + Ok(()) + } +} + +impl<W: Write> ImageEncoder for TgaEncoder<W> { + fn write_image( + self, + buf: &[u8], + width: u32, + height: u32, + color_type: ColorType, + ) -> ImageResult<()> { + self.encode(buf, width, height, color_type) + } +} + +#[cfg(test)] +mod tests { + use super::{EncoderError, TgaEncoder}; + use crate::{codecs::tga::TgaDecoder, ColorType, ImageDecoder, ImageError}; + use std::{error::Error, io::Cursor}; + + fn round_trip_image(image: &[u8], width: u32, height: u32, c: ColorType) -> Vec<u8> { + let mut encoded_data = Vec::new(); + { + let encoder = TgaEncoder::new(&mut encoded_data); + encoder + .encode(&image, width, height, c) + .expect("could not encode image"); + } + + let decoder = TgaDecoder::new(Cursor::new(&encoded_data)).expect("failed to decode"); + + let mut buf = vec![0; decoder.total_bytes() as usize]; + decoder.read_image(&mut buf).expect("failed to decode"); + buf + } + + #[test] + fn test_image_width_too_large() { + // TGA cannot encode images larger than 65,535×65,535 + // create a 65,536×1 8-bit black image buffer + let size = usize::from(u16::MAX) + 1; + let dimension = size as u32; + let img = vec![0u8; size]; + // Try to encode an image that is too large + let mut encoded = Vec::new(); + let encoder = TgaEncoder::new(&mut encoded); + let result = encoder.encode(&img, dimension, 1, ColorType::L8); + match result { + Err(ImageError::Encoding(err)) => { + let err = err + .source() + .unwrap() + .downcast_ref::<EncoderError>() + .unwrap(); + assert_eq!(*err, EncoderError::WidthInvalid(dimension)); + } + other => panic!( + "Encoding an image that is too wide should return a InvalidWidth \ + it returned {:?} instead", + other + ), + } + } + + #[test] + fn test_image_height_too_large() { + // TGA cannot encode images larger than 65,535×65,535 + // create a 65,536×1 8-bit black image buffer + let size = usize::from(u16::MAX) + 1; + let dimension = size as u32; + let img = vec![0u8; size]; + // Try to encode an image that is too large + let mut encoded = Vec::new(); + let encoder = TgaEncoder::new(&mut encoded); + let result = encoder.encode(&img, 1, dimension, ColorType::L8); + match result { + Err(ImageError::Encoding(err)) => { + let err = err + .source() + .unwrap() + .downcast_ref::<EncoderError>() + .unwrap(); + assert_eq!(*err, EncoderError::HeightInvalid(dimension)); + } + other => panic!( + "Encoding an image that is too tall should return a InvalidHeight \ + it returned {:?} instead", + other + ), + } + } + + #[test] + fn round_trip_single_pixel_rgb() { + let image = [0, 1, 2]; + let decoded = round_trip_image(&image, 1, 1, ColorType::Rgb8); + assert_eq!(decoded.len(), image.len()); + assert_eq!(decoded.as_slice(), image); + } + + #[test] + fn round_trip_single_pixel_rgba() { + let image = [0, 1, 2, 3]; + let decoded = round_trip_image(&image, 1, 1, ColorType::Rgba8); + assert_eq!(decoded.len(), image.len()); + assert_eq!(decoded.as_slice(), image); + } + + #[test] + fn round_trip_gray() { + let image = [0, 1, 2]; + let decoded = round_trip_image(&image, 3, 1, ColorType::L8); + assert_eq!(decoded.len(), image.len()); + assert_eq!(decoded.as_slice(), image); + } + + #[test] + fn round_trip_graya() { + let image = [0, 1, 2, 3, 4, 5]; + let decoded = round_trip_image(&image, 1, 3, ColorType::La8); + assert_eq!(decoded.len(), image.len()); + assert_eq!(decoded.as_slice(), image); + } + + #[test] + fn round_trip_3px_rgb() { + let image = [0; 3 * 3 * 3]; // 3x3 pixels, 3 bytes per pixel + let _decoded = round_trip_image(&image, 3, 3, ColorType::Rgb8); + } +} |