1 use super::header::Header; 2 use crate::{error::EncodingError, ColorType, ImageEncoder, ImageError, ImageFormat, ImageResult}; 3 use std::{convert::TryFrom, error, fmt, io::Write}; 4 5 /// Errors that can occur during encoding and saving of a TGA image. 6 #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] 7 enum EncoderError { 8 /// Invalid TGA width. 9 WidthInvalid(u32), 10 11 /// Invalid TGA height. 12 HeightInvalid(u32), 13 } 14 15 impl fmt::Display for EncoderError { fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result16 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 17 match self { 18 EncoderError::WidthInvalid(s) => f.write_fmt(format_args!("Invalid TGA width: {}", s)), 19 EncoderError::HeightInvalid(s) => { 20 f.write_fmt(format_args!("Invalid TGA height: {}", s)) 21 } 22 } 23 } 24 } 25 26 impl From<EncoderError> for ImageError { from(e: EncoderError) -> ImageError27 fn from(e: EncoderError) -> ImageError { 28 ImageError::Encoding(EncodingError::new(ImageFormat::Tga.into(), e)) 29 } 30 } 31 32 impl error::Error for EncoderError {} 33 34 /// TGA encoder. 35 pub struct TgaEncoder<W: Write> { 36 writer: W, 37 } 38 39 impl<W: Write> TgaEncoder<W> { 40 /// Create a new encoder that writes its output to ```w```. new(w: W) -> TgaEncoder<W>41 pub fn new(w: W) -> TgaEncoder<W> { 42 TgaEncoder { writer: w } 43 } 44 45 /// Encodes the image ```buf``` that has dimensions ```width``` 46 /// and ```height``` and ```ColorType``` ```color_type```. 47 /// 48 /// The dimensions of the image must be between 0 and 65535 (inclusive) or 49 /// an error will be returned. encode( mut self, buf: &[u8], width: u32, height: u32, color_type: ColorType, ) -> ImageResult<()>50 pub fn encode( 51 mut self, 52 buf: &[u8], 53 width: u32, 54 height: u32, 55 color_type: ColorType, 56 ) -> ImageResult<()> { 57 // Validate dimensions. 58 let width = u16::try_from(width) 59 .map_err(|_| ImageError::from(EncoderError::WidthInvalid(width)))?; 60 61 let height = u16::try_from(height) 62 .map_err(|_| ImageError::from(EncoderError::HeightInvalid(height)))?; 63 64 // Write out TGA header. 65 let header = Header::from_pixel_info(color_type, width, height)?; 66 header.write_to(&mut self.writer)?; 67 68 // Write out Bgr(a)8 or L(a)8 image data. 69 let mut image = Vec::from(buf); 70 71 match color_type { 72 ColorType::Rgb8 | ColorType::Rgba8 => { 73 for chunk in image.chunks_mut(usize::from(color_type.bytes_per_pixel())) { 74 chunk.swap(0, 2); 75 } 76 } 77 _ => {} 78 } 79 80 self.writer.write_all(&image)?; 81 Ok(()) 82 } 83 } 84 85 impl<W: Write> ImageEncoder for TgaEncoder<W> { write_image( self, buf: &[u8], width: u32, height: u32, color_type: ColorType, ) -> ImageResult<()>86 fn write_image( 87 self, 88 buf: &[u8], 89 width: u32, 90 height: u32, 91 color_type: ColorType, 92 ) -> ImageResult<()> { 93 self.encode(buf, width, height, color_type) 94 } 95 } 96 97 #[cfg(test)] 98 mod tests { 99 use super::{EncoderError, TgaEncoder}; 100 use crate::{tga::TgaDecoder, ColorType, ImageDecoder, ImageError}; 101 use std::{error::Error, io::Cursor}; 102 round_trip_image(image: &[u8], width: u32, height: u32, c: ColorType) -> Vec<u8>103 fn round_trip_image(image: &[u8], width: u32, height: u32, c: ColorType) -> Vec<u8> { 104 let mut encoded_data = Vec::new(); 105 { 106 let encoder = TgaEncoder::new(&mut encoded_data); 107 encoder 108 .encode(&image, width, height, c) 109 .expect("could not encode image"); 110 } 111 112 let decoder = TgaDecoder::new(Cursor::new(&encoded_data)).expect("failed to decode"); 113 114 let mut buf = vec![0; decoder.total_bytes() as usize]; 115 decoder.read_image(&mut buf).expect("failed to decode"); 116 buf 117 } 118 119 #[test] test_image_width_too_large()120 fn test_image_width_too_large() { 121 // TGA cannot encode images larger than 65,535×65,535 122 // create a 65,536×1 8-bit black image buffer 123 let size = usize::from(u16::MAX) + 1; 124 let dimension = size as u32; 125 let img = vec![0u8; size]; 126 // Try to encode an image that is too large 127 let mut encoded = Vec::new(); 128 let encoder = TgaEncoder::new(&mut encoded); 129 let result = encoder.encode(&img, dimension, 1, ColorType::L8); 130 match result { 131 Err(ImageError::Encoding(err)) => { 132 let err = err 133 .source() 134 .unwrap() 135 .downcast_ref::<EncoderError>() 136 .unwrap(); 137 assert_eq!(*err, EncoderError::WidthInvalid(dimension)); 138 } 139 other => panic!( 140 "Encoding an image that is too wide should return a InvalidWidth \ 141 it returned {:?} instead", 142 other 143 ), 144 } 145 } 146 147 #[test] test_image_height_too_large()148 fn test_image_height_too_large() { 149 // TGA cannot encode images larger than 65,535×65,535 150 // create a 65,536×1 8-bit black image buffer 151 let size = usize::from(u16::MAX) + 1; 152 let dimension = size as u32; 153 let img = vec![0u8; size]; 154 // Try to encode an image that is too large 155 let mut encoded = Vec::new(); 156 let encoder = TgaEncoder::new(&mut encoded); 157 let result = encoder.encode(&img, 1, dimension, ColorType::L8); 158 match result { 159 Err(ImageError::Encoding(err)) => { 160 let err = err 161 .source() 162 .unwrap() 163 .downcast_ref::<EncoderError>() 164 .unwrap(); 165 assert_eq!(*err, EncoderError::HeightInvalid(dimension)); 166 } 167 other => panic!( 168 "Encoding an image that is too tall should return a InvalidHeight \ 169 it returned {:?} instead", 170 other 171 ), 172 } 173 } 174 175 #[test] round_trip_single_pixel_rgb()176 fn round_trip_single_pixel_rgb() { 177 let image = [0, 1, 2]; 178 let decoded = round_trip_image(&image, 1, 1, ColorType::Rgb8); 179 assert_eq!(decoded.len(), image.len()); 180 assert_eq!(decoded.as_slice(), image); 181 } 182 183 #[test] round_trip_single_pixel_rgba()184 fn round_trip_single_pixel_rgba() { 185 let image = [0, 1, 2, 3]; 186 let decoded = round_trip_image(&image, 1, 1, ColorType::Rgba8); 187 assert_eq!(decoded.len(), image.len()); 188 assert_eq!(decoded.as_slice(), image); 189 } 190 191 #[test] round_trip_single_pixel_bgr()192 fn round_trip_single_pixel_bgr() { 193 let image = [0, 1, 2]; 194 let decoded = round_trip_image(&image, 1, 1, ColorType::Bgr8); 195 assert_eq!(decoded.len(), image.len()); 196 assert_eq!(decoded.as_slice(), [2, 1, 0]); 197 } 198 199 #[test] round_trip_single_pixel_bgra()200 fn round_trip_single_pixel_bgra() { 201 let image = [0, 1, 2, 3]; 202 let decoded = round_trip_image(&image, 1, 1, ColorType::Bgra8); 203 assert_eq!(decoded.len(), image.len()); 204 assert_eq!(decoded.as_slice(), [2, 1, 0, 3]); 205 } 206 207 #[test] round_trip_gray()208 fn round_trip_gray() { 209 let image = [0, 1, 2]; 210 let decoded = round_trip_image(&image, 3, 1, ColorType::L8); 211 assert_eq!(decoded.len(), image.len()); 212 assert_eq!(decoded.as_slice(), image); 213 } 214 215 #[test] round_trip_graya()216 fn round_trip_graya() { 217 let image = [0, 1, 2, 3, 4, 5]; 218 let decoded = round_trip_image(&image, 1, 3, ColorType::La8); 219 assert_eq!(decoded.len(), image.len()); 220 assert_eq!(decoded.as_slice(), image); 221 } 222 223 #[test] round_trip_3px_rgb()224 fn round_trip_3px_rgb() { 225 let image = [0; 3 * 3 * 3]; // 3x3 pixels, 3 bytes per pixel 226 let _decoded = round_trip_image(&image, 3, 3, ColorType::Rgb8); 227 } 228 } 229