image/codecs/tga/
encoder.rs

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