image/codecs/tga/
encoder.rs

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