Coverage Report

Created: 2025-07-18 06:49

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