Coverage Report

Created: 2026-08-31 07:42

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/image/src/codecs/tga/encoder.rs
Line
Count
Source
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
    /// Empty
16
    Empty(u32, u32),
17
}
18
19
impl fmt::Display for EncoderError {
20
0
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
21
0
        match self {
22
0
            EncoderError::WidthInvalid(s) => f.write_fmt(format_args!("Invalid TGA width: {s}")),
23
0
            EncoderError::HeightInvalid(s) => f.write_fmt(format_args!("Invalid TGA height: {s}")),
24
0
            EncoderError::Empty(w, h) => f.write_fmt(format_args!("Invalid TGA size: {w}x{h}")),
25
        }
26
0
    }
27
}
28
29
impl From<EncoderError> for ImageError {
30
0
    fn from(e: EncoderError) -> ImageError {
31
0
        ImageError::Encoding(EncodingError::new(ImageFormat::Tga.into(), e))
32
0
    }
33
}
34
35
impl error::Error for EncoderError {}
36
37
/// TGA encoder.
38
pub struct TgaEncoder<W: Write> {
39
    writer: W,
40
41
    /// Run-length encoding
42
    use_rle: bool,
43
}
44
45
const MAX_RUN_LENGTH: u8 = 128;
46
47
#[derive(Debug, Eq, PartialEq)]
48
enum PacketType {
49
    Raw,
50
    Rle,
51
}
52
53
impl<W: Write> TgaEncoder<W> {
54
    /// Create a new encoder that writes its output to ```w```.
55
0
    pub fn new(w: W) -> TgaEncoder<W> {
56
0
        TgaEncoder {
57
0
            writer: w,
58
0
            use_rle: true,
59
0
        }
60
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
61
62
    /// Disables run-length encoding
63
0
    pub fn disable_rle(mut self) -> TgaEncoder<W> {
64
0
        self.use_rle = false;
65
0
        self
66
0
    }
67
68
    /// Writes a raw packet to the writer
69
0
    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
0
        let header = counter - 1;
73
0
        self.writer.write_all(&[header])?;
74
0
        self.writer.write_all(pixels)?;
75
0
        Ok(())
76
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
77
78
    /// Writes a run-length encoded packet to the writer
79
0
    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
0
        let header = 0x80 | (counter - 1);
82
0
        self.writer.write_all(&[header])?;
83
0
        self.writer.write_all(pixel)?;
84
0
        Ok(())
85
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
86
87
    /// Writes the run-length encoded buffer to the writer
88
0
    fn run_length_encode(
89
0
        &mut self,
90
0
        image: &[u8],
91
0
        color_type: ExtendedColorType,
92
0
    ) -> ImageResult<()> {
93
        use PacketType::*;
94
95
0
        let bytes_per_pixel = color_type.bits_per_pixel() / 8;
96
0
        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
0
        let mut buf = vec_try_with_capacity(capacity_in_bytes)?;
101
102
0
        let mut counter = 0;
103
0
        let mut prev_pixel = None;
104
0
        let mut packet_type = Rle;
105
106
0
        for pixel in image.chunks(usize::from(bytes_per_pixel)) {
107
            // Make sure we are not at the first pixel
108
0
            if let Some(prev) = prev_pixel {
109
0
                if pixel == prev {
110
0
                    if packet_type == Raw && counter > 0 {
111
0
                        self.write_raw_packet(&buf, counter)?;
112
0
                        counter = 0;
113
0
                        buf.clear();
114
0
                    }
115
116
0
                    packet_type = Rle;
117
0
                } else if packet_type == Rle && counter > 0 {
118
0
                    self.write_rle_encoded_packet(prev, counter)?;
119
0
                    counter = 0;
120
0
                    packet_type = Raw;
121
0
                    buf.clear();
122
0
                }
123
0
            }
124
125
0
            counter += 1;
126
0
            buf.extend_from_slice(pixel);
127
128
0
            debug_assert!(buf.len() <= capacity_in_bytes);
129
130
0
            if counter == MAX_RUN_LENGTH {
131
0
                match packet_type {
132
0
                    Rle => self.write_rle_encoded_packet(prev_pixel.unwrap(), counter),
133
0
                    Raw => self.write_raw_packet(&buf, counter),
134
0
                }?;
135
136
0
                counter = 0;
137
0
                packet_type = Rle;
138
0
                buf.clear();
139
0
            }
140
141
0
            prev_pixel = Some(pixel);
142
        }
143
144
0
        if counter > 0 {
145
0
            match packet_type {
146
0
                Rle => self.write_rle_encoded_packet(prev_pixel.unwrap(), counter),
147
0
                Raw => self.write_raw_packet(&buf, counter),
148
0
            }?;
149
0
        }
150
151
0
        Ok(())
152
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
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 the buffer does not hold exactly the number of bytes required for the given
163
    /// `width`, `height`, and `color_type`, accounting for rows padded to whole bytes for
164
    /// sub-byte color types: `height * ((width * color_type.bits_per_pixel() as u32 + 7) / 8)`.
165
    #[track_caller]
166
0
    pub fn encode(
167
0
        mut self,
168
0
        buf: &[u8],
169
0
        width: u32,
170
0
        height: u32,
171
0
        color_type: ExtendedColorType,
172
0
    ) -> ImageResult<()> {
173
0
        let expected_buffer_len = color_type.buffer_size(width, height);
174
0
        assert_eq!(
175
            expected_buffer_len,
176
0
            buf.len() as u64,
177
0
            "Invalid buffer length: expected {expected_buffer_len} got {} for {width}x{height} image",
178
0
            buf.len(),
179
        );
180
181
        // Validate dimensions.
182
0
        if width == 0 || height == 0 {
183
0
            return Err(ImageError::from(EncoderError::Empty(width, height)));
184
0
        }
185
186
0
        let width = u16::try_from(width)
187
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}
188
189
0
        let height = u16::try_from(height)
190
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}
191
192
        // Write out TGA header.
193
0
        let header = Header::from_pixel_info(color_type, width, height, self.use_rle)?;
194
0
        header.write_to(&mut self.writer)?;
195
196
0
        let image_type = ImageType::new(header.image_type);
197
198
0
        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
0
                match color_type {
204
                    ExtendedColorType::Rgb8 | ExtendedColorType::Rgba8 => {
205
0
                        let mut image = Vec::from(buf);
206
207
0
                        for pixel in image.chunks_mut(usize::from(color_type.bits_per_pixel() / 8))
208
0
                        {
209
0
                            pixel.swap(0, 2);
210
0
                        }
211
212
0
                        self.run_length_encode(&image, color_type)?;
213
                    }
214
                    _ => {
215
0
                        self.run_length_encode(buf, color_type)?;
216
                    }
217
                }
218
            }
219
            _ => {
220
                // Write uncompressed image data
221
222
0
                match color_type {
223
                    ExtendedColorType::Rgb8 | ExtendedColorType::Rgba8 => {
224
0
                        let mut image = Vec::from(buf);
225
226
0
                        for pixel in image.chunks_mut(usize::from(color_type.bits_per_pixel() / 8))
227
0
                        {
228
0
                            pixel.swap(0, 2);
229
0
                        }
230
231
0
                        self.writer.write_all(&image)?;
232
                    }
233
                    _ => {
234
0
                        self.writer.write_all(buf)?;
235
                    }
236
                }
237
            }
238
        }
239
240
0
        Ok(())
241
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
242
}
243
244
impl<W: Write> ImageEncoder for TgaEncoder<W> {
245
    #[track_caller]
246
0
    fn write_image(
247
0
        self,
248
0
        buf: &[u8],
249
0
        width: u32,
250
0
        height: u32,
251
0
        color_type: ExtendedColorType,
252
0
    ) -> ImageResult<()> {
253
0
        self.encode(buf, width, height, color_type)
254
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
255
256
0
    fn make_compatible_img(
257
0
        &self,
258
0
        _: crate::io::encoder::MethodSealedToImage,
259
0
        img: &DynamicImage,
260
0
    ) -> Option<DynamicImage> {
261
0
        crate::io::encoder::dynimage_conversion_8bit(img)
262
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
263
}
264
265
#[cfg(test)]
266
mod 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
373
            let mut decoder =
374
                TgaDecoder::new(Cursor::new(&encoded_data)).expect("failed to decode");
375
            let layout = decoder.prepare_image().unwrap();
376
            let mut buf = vec![0; layout.total_bytes() as usize];
377
            decoder.read_image(&mut buf).expect("failed to decode");
378
            buf
379
        }
380
381
        #[test]
382
        fn mixed_packets() {
383
            let image = [
384
                255, 255, 255, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255,
385
            ];
386
            let decoded = round_trip_image(&image, 5, 1, ExtendedColorType::Rgb8);
387
            assert_eq!(decoded.len(), image.len());
388
            assert_eq!(decoded.as_slice(), image);
389
        }
390
391
        #[test]
392
        fn round_trip_gray() {
393
            let image = [0, 1, 2];
394
            let decoded = round_trip_image(&image, 3, 1, ExtendedColorType::L8);
395
            assert_eq!(decoded.len(), image.len());
396
            assert_eq!(decoded.as_slice(), image);
397
        }
398
399
        #[test]
400
        fn round_trip_graya() {
401
            let image = [0, 1, 2, 3, 4, 5];
402
            let decoded = round_trip_image(&image, 1, 3, ExtendedColorType::La8);
403
            assert_eq!(decoded.len(), image.len());
404
            assert_eq!(decoded.as_slice(), image);
405
        }
406
407
        #[test]
408
        fn round_trip_single_pixel_rgb() {
409
            let image = [0, 1, 2];
410
            let decoded = round_trip_image(&image, 1, 1, ExtendedColorType::Rgb8);
411
            assert_eq!(decoded.len(), image.len());
412
            assert_eq!(decoded.as_slice(), image);
413
        }
414
415
        #[test]
416
        fn round_trip_three_pixel_rgb() {
417
            let image = [0, 1, 2, 0, 1, 2, 0, 1, 2];
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_3px_rgb() {
425
            let image = [0; 3 * 3 * 3]; // 3x3 pixels, 3 bytes per pixel
426
            let decoded = round_trip_image(&image, 3, 3, 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() {
433
            let image = [0, 1, 2, 0, 1, 3, 0, 1, 4];
434
            let decoded = round_trip_image(&image, 3, 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_different_2() {
441
            let image = [0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 4];
442
            let decoded = round_trip_image(&image, 4, 1, ExtendedColorType::Rgb8);
443
            assert_eq!(decoded.len(), image.len());
444
            assert_eq!(decoded.as_slice(), image);
445
        }
446
447
        #[test]
448
        fn round_trip_different_3() {
449
            let image = [0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 4, 0, 1, 2];
450
            let decoded = round_trip_image(&image, 5, 1, ExtendedColorType::Rgb8);
451
            assert_eq!(decoded.len(), image.len());
452
            assert_eq!(decoded.as_slice(), image);
453
        }
454
455
        #[test]
456
        fn round_trip_bw() {
457
            // This example demonstrates the run-length counter being saturated
458
            // It should never overflow and can be 128 max
459
            let image = crate::open("tests/images/tga/encoding/black_white.tga").unwrap();
460
            let (width, height) = (image.width(), image.height());
461
            let image = image.as_rgb8().unwrap().to_vec();
462
463
            let decoded = round_trip_image(&image, width, height, ExtendedColorType::Rgb8);
464
            assert_eq!(decoded.len(), image.len());
465
            assert_eq!(decoded.as_slice(), image);
466
        }
467
    }
468
469
    mod uncompressed {
470
        use super::*;
471
472
        fn round_trip_image(
473
            image: &[u8],
474
            width: u32,
475
            height: u32,
476
            c: ExtendedColorType,
477
        ) -> Vec<u8> {
478
            let mut encoded_data = Vec::new();
479
            {
480
                let encoder = TgaEncoder::new(&mut encoded_data).disable_rle();
481
                encoder
482
                    .encode(image, width, height, c)
483
                    .expect("could not encode image");
484
            }
485
486
            let mut decoder =
487
                TgaDecoder::new(Cursor::new(&encoded_data)).expect("failed to decode");
488
            let layout = decoder.prepare_image().unwrap();
489
            let mut buf = vec![0; layout.total_bytes() as usize];
490
            decoder.read_image(&mut buf).expect("failed to decode");
491
            buf
492
        }
493
494
        #[test]
495
        fn round_trip_single_pixel_rgb() {
496
            let image = [0, 1, 2];
497
            let decoded = round_trip_image(&image, 1, 1, ExtendedColorType::Rgb8);
498
            assert_eq!(decoded.len(), image.len());
499
            assert_eq!(decoded.as_slice(), image);
500
        }
501
502
        #[test]
503
        fn round_trip_single_pixel_rgba() {
504
            let image = [0, 1, 2, 3];
505
            let decoded = round_trip_image(&image, 1, 1, ExtendedColorType::Rgba8);
506
            assert_eq!(decoded.len(), image.len());
507
            assert_eq!(decoded.as_slice(), image);
508
        }
509
510
        #[test]
511
        fn round_trip_gray() {
512
            let image = [0, 1, 2];
513
            let decoded = round_trip_image(&image, 3, 1, ExtendedColorType::L8);
514
            assert_eq!(decoded.len(), image.len());
515
            assert_eq!(decoded.as_slice(), image);
516
        }
517
518
        #[test]
519
        fn round_trip_graya() {
520
            let image = [0, 1, 2, 3, 4, 5];
521
            let decoded = round_trip_image(&image, 1, 3, ExtendedColorType::La8);
522
            assert_eq!(decoded.len(), image.len());
523
            assert_eq!(decoded.as_slice(), image);
524
        }
525
526
        #[test]
527
        fn round_trip_3px_rgb() {
528
            let image = [0; 3 * 3 * 3]; // 3x3 pixels, 3 bytes per pixel
529
            let decoded = round_trip_image(&image, 3, 3, ExtendedColorType::Rgb8);
530
            assert_eq!(decoded.len(), image.len());
531
            assert_eq!(decoded.as_slice(), image);
532
        }
533
    }
534
}