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