image/codecs/tga/
encoder.rs

1use super::header::Header;
2use crate::{
3    codecs::tga::header::ImageType, error::EncodingError, ColorType, ImageEncoder, ImageError,
4    ImageFormat, ImageResult,
5};
6use std::{error, fmt, io::Write};
7
8/// Errors that can occur during encoding and saving of a TGA image.
9#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
10enum EncoderError {
11    /// Invalid TGA width.
12    WidthInvalid(u32),
13
14    /// Invalid TGA height.
15    HeightInvalid(u32),
16}
17
18impl fmt::Display for EncoderError {
19    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
20        match self {
21            EncoderError::WidthInvalid(s) => f.write_fmt(format_args!("Invalid TGA width: {}", s)),
22            EncoderError::HeightInvalid(s) => {
23                f.write_fmt(format_args!("Invalid TGA height: {}", s))
24            }
25        }
26    }
27}
28
29impl From<EncoderError> for ImageError {
30    fn from(e: EncoderError) -> ImageError {
31        ImageError::Encoding(EncodingError::new(ImageFormat::Tga.into(), e))
32    }
33}
34
35impl error::Error for EncoderError {}
36
37/// TGA encoder.
38pub struct TgaEncoder<W: Write> {
39    writer: W,
40
41    /// Run-length encoding
42    use_rle: bool,
43}
44
45const MAX_RUN_LENGTH: u8 = 128;
46
47#[derive(Debug, Eq, PartialEq)]
48enum PacketType {
49    Raw,
50    Rle,
51}
52
53impl<W: Write> TgaEncoder<W> {
54    /// Create a new encoder that writes its output to ```w```.
55    pub fn new(w: W) -> TgaEncoder<W> {
56        TgaEncoder {
57            writer: w,
58            use_rle: true,
59        }
60    }
61
62    /// Disables run-length encoding
63    pub fn disable_rle(mut self) -> TgaEncoder<W> {
64        self.use_rle = false;
65        self
66    }
67
68    /// Writes a raw packet to the writer
69    fn write_raw_packet(&mut self, pixels: &[u8], counter: u8) -> ImageResult<()> {
70        // Set high bit = 0 and store counter - 1 (because 0 would be useless)
71        // The counter fills 7 bits max, so the high bit is set to 0 implicitly
72        let header = counter - 1;
73        self.writer.write_all(&[header])?;
74        self.writer.write_all(pixels)?;
75        Ok(())
76    }
77
78    /// Writes a run-length encoded packet to the writer
79    fn write_rle_encoded_packet(&mut self, pixel: &[u8], counter: u8) -> ImageResult<()> {
80        // Set high bit = 1 and store counter - 1 (because 0 would be useless)
81        let header = 0x80 | (counter - 1);
82        self.writer.write_all(&[header])?;
83        self.writer.write_all(pixel)?;
84        Ok(())
85    }
86
87    /// Writes the run-length encoded buffer to the writer
88    fn run_length_encode(&mut self, image: &[u8], color_type: ColorType) -> ImageResult<()> {
89        use PacketType::*;
90
91        let bytes_per_pixel = color_type.bytes_per_pixel();
92        let capacity_in_bytes = usize::from(MAX_RUN_LENGTH) * usize::from(bytes_per_pixel);
93
94        // Buffer to temporarily store pixels
95        // so we can choose whether to use RLE or not when we need to
96        let mut buf = Vec::with_capacity(capacity_in_bytes);
97
98        let mut counter = 0;
99        let mut prev_pixel = None;
100        let mut packet_type = Rle;
101
102        for pixel in image.chunks(usize::from(bytes_per_pixel)) {
103            // Make sure we are not at the first pixel
104            if let Some(prev) = prev_pixel {
105                if pixel == prev {
106                    if packet_type == Raw && counter > 0 {
107                        self.write_raw_packet(&buf, counter)?;
108                        counter = 0;
109                        buf.clear();
110                    }
111
112                    packet_type = Rle;
113                } else if packet_type == Rle && counter > 0 {
114                    self.write_rle_encoded_packet(prev, counter)?;
115                    counter = 0;
116                    packet_type = Raw;
117                    buf.clear();
118                }
119            }
120
121            counter += 1;
122            buf.extend_from_slice(pixel);
123
124            debug_assert!(buf.len() <= capacity_in_bytes);
125
126            if counter == MAX_RUN_LENGTH {
127                match packet_type {
128                    Rle => self.write_rle_encoded_packet(prev_pixel.unwrap(), counter),
129                    Raw => self.write_raw_packet(&buf, counter),
130                }?;
131
132                counter = 0;
133                packet_type = Rle;
134                buf.clear();
135            }
136
137            prev_pixel = Some(pixel);
138        }
139
140        if counter > 0 {
141            match packet_type {
142                Rle => self.write_rle_encoded_packet(prev_pixel.unwrap(), counter),
143                Raw => self.write_raw_packet(&buf, counter),
144            }?;
145        }
146
147        Ok(())
148    }
149
150    /// Encodes the image ```buf``` that has dimensions ```width```
151    /// and ```height``` and ```ColorType``` ```color_type```.
152    ///
153    /// The dimensions of the image must be between 0 and 65535 (inclusive) or
154    /// an error will be returned.
155    ///
156    /// # Panics
157    ///
158    /// Panics if `width * height * color_type.bytes_per_pixel() != data.len()`.
159    #[track_caller]
160    pub fn encode(
161        mut self,
162        buf: &[u8],
163        width: u32,
164        height: u32,
165        color_type: ColorType,
166    ) -> ImageResult<()> {
167        let expected_buffer_len =
168            (width as u64 * height as u64).saturating_mul(color_type.bytes_per_pixel() as u64);
169        assert_eq!(
170            expected_buffer_len,
171            buf.len() as u64,
172            "Invalid buffer length: expected {expected_buffer_len} got {} for {width}x{height} image",
173            buf.len(),
174        );
175
176        // Validate dimensions.
177        let width = u16::try_from(width)
178            .map_err(|_| ImageError::from(EncoderError::WidthInvalid(width)))?;
179
180        let height = u16::try_from(height)
181            .map_err(|_| ImageError::from(EncoderError::HeightInvalid(height)))?;
182
183        // Write out TGA header.
184        let header = Header::from_pixel_info(color_type, width, height, self.use_rle)?;
185        header.write_to(&mut self.writer)?;
186
187        let image_type = ImageType::new(header.image_type);
188
189        match image_type {
190            //TODO: support RunColorMap, and change match to image_type.is_encoded()
191            ImageType::RunTrueColor | ImageType::RunGrayScale => {
192                // Write run-length encoded image data
193
194                match color_type {
195                    ColorType::Rgb8 | ColorType::Rgba8 => {
196                        let mut image = Vec::from(buf);
197
198                        for pixel in image.chunks_mut(usize::from(color_type.bytes_per_pixel())) {
199                            pixel.swap(0, 2);
200                        }
201
202                        self.run_length_encode(&image, color_type)?;
203                    }
204                    _ => {
205                        self.run_length_encode(buf, color_type)?;
206                    }
207                }
208            }
209            _ => {
210                // Write uncompressed image data
211
212                match color_type {
213                    ColorType::Rgb8 | ColorType::Rgba8 => {
214                        let mut image = Vec::from(buf);
215
216                        for pixel in image.chunks_mut(usize::from(color_type.bytes_per_pixel())) {
217                            pixel.swap(0, 2);
218                        }
219
220                        self.writer.write_all(&image)?;
221                    }
222                    _ => {
223                        self.writer.write_all(buf)?;
224                    }
225                }
226            }
227        }
228
229        Ok(())
230    }
231}
232
233impl<W: Write> ImageEncoder for TgaEncoder<W> {
234    #[track_caller]
235    fn write_image(
236        self,
237        buf: &[u8],
238        width: u32,
239        height: u32,
240        color_type: ColorType,
241    ) -> ImageResult<()> {
242        self.encode(buf, width, height, color_type)
243    }
244}
245
246#[cfg(test)]
247mod tests {
248    use super::{EncoderError, TgaEncoder};
249    use crate::{codecs::tga::TgaDecoder, ColorType, ImageDecoder, ImageError};
250    use std::{error::Error, io::Cursor};
251
252    #[test]
253    fn test_image_width_too_large() {
254        // TGA cannot encode images larger than 65,535×65,535
255        // create a 65,536×1 8-bit black image buffer
256        let size = usize::from(u16::MAX) + 1;
257        let dimension = size as u32;
258        let img = vec![0u8; size];
259
260        // Try to encode an image that is too large
261        let mut encoded = Vec::new();
262        let encoder = TgaEncoder::new(&mut encoded);
263        let result = encoder.encode(&img, dimension, 1, ColorType::L8);
264
265        match result {
266            Err(ImageError::Encoding(err)) => {
267                let err = err
268                    .source()
269                    .unwrap()
270                    .downcast_ref::<EncoderError>()
271                    .unwrap();
272                assert_eq!(*err, EncoderError::WidthInvalid(dimension));
273            }
274            other => panic!(
275                "Encoding an image that is too wide should return a InvalidWidth \
276                it returned {:?} instead",
277                other
278            ),
279        }
280    }
281
282    #[test]
283    fn test_image_height_too_large() {
284        // TGA cannot encode images larger than 65,535×65,535
285        // create a 65,536×1 8-bit black image buffer
286        let size = usize::from(u16::MAX) + 1;
287        let dimension = size as u32;
288        let img = vec![0u8; size];
289
290        // Try to encode an image that is too large
291        let mut encoded = Vec::new();
292        let encoder = TgaEncoder::new(&mut encoded);
293        let result = encoder.encode(&img, 1, dimension, ColorType::L8);
294
295        match result {
296            Err(ImageError::Encoding(err)) => {
297                let err = err
298                    .source()
299                    .unwrap()
300                    .downcast_ref::<EncoderError>()
301                    .unwrap();
302                assert_eq!(*err, EncoderError::HeightInvalid(dimension));
303            }
304            other => panic!(
305                "Encoding an image that is too tall should return a InvalidHeight \
306                it returned {:?} instead",
307                other
308            ),
309        }
310    }
311
312    #[test]
313    fn test_compression_diff() {
314        let image = [0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2];
315
316        let uncompressed_bytes = {
317            let mut encoded_data = Vec::new();
318            let encoder = TgaEncoder::new(&mut encoded_data).disable_rle();
319            encoder
320                .encode(&image, 5, 1, ColorType::Rgb8)
321                .expect("could not encode image");
322
323            encoded_data
324        };
325
326        let compressed_bytes = {
327            let mut encoded_data = Vec::new();
328            let encoder = TgaEncoder::new(&mut encoded_data);
329            encoder
330                .encode(&image, 5, 1, ColorType::Rgb8)
331                .expect("could not encode image");
332
333            encoded_data
334        };
335
336        assert!(uncompressed_bytes.len() > compressed_bytes.len());
337    }
338
339    mod compressed {
340        use super::*;
341
342        fn round_trip_image(image: &[u8], width: u32, height: u32, c: ColorType) -> Vec<u8> {
343            let mut encoded_data = Vec::new();
344            {
345                let encoder = TgaEncoder::new(&mut encoded_data);
346                encoder
347                    .encode(image, width, height, c)
348                    .expect("could not encode image");
349            }
350            let decoder = TgaDecoder::new(Cursor::new(&encoded_data)).expect("failed to decode");
351
352            let mut buf = vec![0; decoder.total_bytes() as usize];
353            decoder.read_image(&mut buf).expect("failed to decode");
354            buf
355        }
356
357        #[test]
358        fn mixed_packets() {
359            let image = [
360                255, 255, 255, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255,
361            ];
362            let decoded = round_trip_image(&image, 5, 1, ColorType::Rgb8);
363            assert_eq!(decoded.len(), image.len());
364            assert_eq!(decoded.as_slice(), image);
365        }
366
367        #[test]
368        fn round_trip_gray() {
369            let image = [0, 1, 2];
370            let decoded = round_trip_image(&image, 3, 1, ColorType::L8);
371            assert_eq!(decoded.len(), image.len());
372            assert_eq!(decoded.as_slice(), image);
373        }
374
375        #[test]
376        fn round_trip_graya() {
377            let image = [0, 1, 2, 3, 4, 5];
378            let decoded = round_trip_image(&image, 1, 3, ColorType::La8);
379            assert_eq!(decoded.len(), image.len());
380            assert_eq!(decoded.as_slice(), image);
381        }
382
383        #[test]
384        fn round_trip_single_pixel_rgb() {
385            let image = [0, 1, 2];
386            let decoded = round_trip_image(&image, 1, 1, ColorType::Rgb8);
387            assert_eq!(decoded.len(), image.len());
388            assert_eq!(decoded.as_slice(), image);
389        }
390
391        #[test]
392        fn round_trip_three_pixel_rgb() {
393            let image = [0, 1, 2, 0, 1, 2, 0, 1, 2];
394            let decoded = round_trip_image(&image, 3, 1, ColorType::Rgb8);
395            assert_eq!(decoded.len(), image.len());
396            assert_eq!(decoded.as_slice(), image);
397        }
398
399        #[test]
400        fn round_trip_3px_rgb() {
401            let image = [0; 3 * 3 * 3]; // 3x3 pixels, 3 bytes per pixel
402            let decoded = round_trip_image(&image, 3, 3, ColorType::Rgb8);
403            assert_eq!(decoded.len(), image.len());
404            assert_eq!(decoded.as_slice(), image);
405        }
406
407        #[test]
408        fn round_trip_different() {
409            let image = [0, 1, 2, 0, 1, 3, 0, 1, 4];
410            let decoded = round_trip_image(&image, 3, 1, ColorType::Rgb8);
411            assert_eq!(decoded.len(), image.len());
412            assert_eq!(decoded.as_slice(), image);
413        }
414
415        #[test]
416        fn round_trip_different_2() {
417            let image = [0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 4];
418            let decoded = round_trip_image(&image, 4, 1, ColorType::Rgb8);
419            assert_eq!(decoded.len(), image.len());
420            assert_eq!(decoded.as_slice(), image);
421        }
422
423        #[test]
424        fn round_trip_different_3() {
425            let image = [0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 4, 0, 1, 2];
426            let decoded = round_trip_image(&image, 5, 1, ColorType::Rgb8);
427            assert_eq!(decoded.len(), image.len());
428            assert_eq!(decoded.as_slice(), image);
429        }
430
431        #[test]
432        fn round_trip_bw() {
433            // This example demonstrates the run-length counter being saturated
434            // It should never overflow and can be 128 max
435            let image = crate::open("tests/images/tga/encoding/black_white.tga").unwrap();
436            let (width, height) = (image.width(), image.height());
437            let image = image.as_rgb8().unwrap().to_vec();
438
439            let decoded = round_trip_image(&image, width, height, ColorType::Rgb8);
440            assert_eq!(decoded.len(), image.len());
441            assert_eq!(decoded.as_slice(), image);
442        }
443    }
444
445    mod uncompressed {
446        use super::*;
447
448        fn round_trip_image(image: &[u8], width: u32, height: u32, c: ColorType) -> Vec<u8> {
449            let mut encoded_data = Vec::new();
450            {
451                let encoder = TgaEncoder::new(&mut encoded_data).disable_rle();
452                encoder
453                    .encode(image, width, height, c)
454                    .expect("could not encode image");
455            }
456
457            let decoder = TgaDecoder::new(Cursor::new(&encoded_data)).expect("failed to decode");
458
459            let mut buf = vec![0; decoder.total_bytes() as usize];
460            decoder.read_image(&mut buf).expect("failed to decode");
461            buf
462        }
463
464        #[test]
465        fn round_trip_single_pixel_rgb() {
466            let image = [0, 1, 2];
467            let decoded = round_trip_image(&image, 1, 1, ColorType::Rgb8);
468            assert_eq!(decoded.len(), image.len());
469            assert_eq!(decoded.as_slice(), image);
470        }
471
472        #[test]
473        fn round_trip_single_pixel_rgba() {
474            let image = [0, 1, 2, 3];
475            let decoded = round_trip_image(&image, 1, 1, ColorType::Rgba8);
476            assert_eq!(decoded.len(), image.len());
477            assert_eq!(decoded.as_slice(), image);
478        }
479
480        #[test]
481        fn round_trip_gray() {
482            let image = [0, 1, 2];
483            let decoded = round_trip_image(&image, 3, 1, ColorType::L8);
484            assert_eq!(decoded.len(), image.len());
485            assert_eq!(decoded.as_slice(), image);
486        }
487
488        #[test]
489        fn round_trip_graya() {
490            let image = [0, 1, 2, 3, 4, 5];
491            let decoded = round_trip_image(&image, 1, 3, ColorType::La8);
492            assert_eq!(decoded.len(), image.len());
493            assert_eq!(decoded.as_slice(), image);
494        }
495
496        #[test]
497        fn round_trip_3px_rgb() {
498            let image = [0; 3 * 3 * 3]; // 3x3 pixels, 3 bytes per pixel
499            let decoded = round_trip_image(&image, 3, 3, ColorType::Rgb8);
500            assert_eq!(decoded.len(), image.len());
501            assert_eq!(decoded.as_slice(), image);
502        }
503    }
504}