Coverage Report

Created: 2025-12-05 07:37

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 `width * height * color_type.bytes_per_pixel() != data.len()`.
163
    #[track_caller]
164
0
    pub fn encode(
165
0
        mut self,
166
0
        buf: &[u8],
167
0
        width: u32,
168
0
        height: u32,
169
0
        color_type: ExtendedColorType,
170
0
    ) -> ImageResult<()> {
171
0
        let expected_buffer_len = color_type.buffer_size(width, height);
172
0
        assert_eq!(
173
            expected_buffer_len,
174
0
            buf.len() as u64,
175
0
            "Invalid buffer length: expected {expected_buffer_len} got {} for {width}x{height} image",
176
0
            buf.len(),
177
        );
178
179
        // Validate dimensions.
180
0
        if width == 0 || height == 0 {
181
0
            return Err(ImageError::from(EncoderError::Empty(width, height)));
182
0
        }
183
184
0
        let width = u16::try_from(width)
185
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}
186
187
0
        let height = u16::try_from(height)
188
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}
189
190
        // Write out TGA header.
191
0
        let header = Header::from_pixel_info(color_type, width, height, self.use_rle)?;
192
0
        header.write_to(&mut self.writer)?;
193
194
0
        let image_type = ImageType::new(header.image_type);
195
196
0
        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
0
                match color_type {
202
                    ExtendedColorType::Rgb8 | ExtendedColorType::Rgba8 => {
203
0
                        let mut image = Vec::from(buf);
204
205
0
                        for pixel in image.chunks_mut(usize::from(color_type.bits_per_pixel() / 8))
206
0
                        {
207
0
                            pixel.swap(0, 2);
208
0
                        }
209
210
0
                        self.run_length_encode(&image, color_type)?;
211
                    }
212
                    _ => {
213
0
                        self.run_length_encode(buf, color_type)?;
214
                    }
215
                }
216
            }
217
            _ => {
218
                // Write uncompressed image data
219
220
0
                match color_type {
221
                    ExtendedColorType::Rgb8 | ExtendedColorType::Rgba8 => {
222
0
                        let mut image = Vec::from(buf);
223
224
0
                        for pixel in image.chunks_mut(usize::from(color_type.bits_per_pixel() / 8))
225
0
                        {
226
0
                            pixel.swap(0, 2);
227
0
                        }
228
229
0
                        self.writer.write_all(&image)?;
230
                    }
231
                    _ => {
232
0
                        self.writer.write_all(buf)?;
233
                    }
234
                }
235
            }
236
        }
237
238
0
        Ok(())
239
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
240
}
241
242
impl<W: Write> ImageEncoder for TgaEncoder<W> {
243
    #[track_caller]
244
0
    fn write_image(
245
0
        self,
246
0
        buf: &[u8],
247
0
        width: u32,
248
0
        height: u32,
249
0
        color_type: ExtendedColorType,
250
0
    ) -> ImageResult<()> {
251
0
        self.encode(buf, width, height, color_type)
252
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
253
254
0
    fn make_compatible_img(
255
0
        &self,
256
0
        _: crate::io::encoder::MethodSealedToImage,
257
0
        img: &DynamicImage,
258
0
    ) -> Option<DynamicImage> {
259
0
        crate::io::encoder::dynimage_conversion_8bit(img)
260
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
261
}
262
263
#[cfg(test)]
264
mod 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
}