Skip to main content

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