Coverage Report

Created: 2026-03-10 07:34

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/image/src/codecs/tga/decoder.rs
Line
Count
Source
1
use super::header::{Header, ImageType, ALPHA_BIT_MASK};
2
use crate::error::DecodingError;
3
use crate::io::ReadExt;
4
use crate::utils::vec_try_with_capacity;
5
use crate::{
6
    color::{ColorType, ExtendedColorType},
7
    error::{ImageError, ImageResult, UnsupportedError, UnsupportedErrorKind},
8
    ImageDecoder, ImageFormat,
9
};
10
use byteorder_lite::ReadBytesExt;
11
use std::io::{self, Read};
12
13
struct ColorMap {
14
    /// sizes in bytes
15
    start_offset: usize,
16
    entry_size: usize,
17
    bytes: Vec<u8>,
18
}
19
20
impl ColorMap {
21
    /// Get one entry from the color map
22
1.88M
    pub(crate) fn get(&self, index: usize) -> Option<&[u8]> {
23
1.88M
        let entry = self.entry_size * index.checked_sub(self.start_offset)?;
24
1.88M
        self.bytes.get(entry..entry + self.entry_size)
25
1.88M
    }
26
}
27
28
/// The representation of a TGA decoder
29
pub struct TgaDecoder<R> {
30
    r: R,
31
32
    width: usize,
33
    height: usize,
34
35
    // The number of bytes in the raw input data for each pixel. If a color map is used, this is the
36
    // number of bytes for each color map index.
37
    raw_bytes_per_pixel: usize,
38
39
    image_type: ImageType,
40
    color_type: ColorType,
41
    original_color_type: Option<ExtendedColorType>,
42
43
    header: Header,
44
    color_map: Option<ColorMap>,
45
}
46
47
#[derive(Copy, Clone, Debug, PartialOrd, PartialEq)]
48
enum TgaOrientation {
49
    TopLeft,
50
    TopRight,
51
    BottomRight,
52
    BottomLeft,
53
}
54
55
impl TgaOrientation {
56
489
    fn from_image_desc_byte(value: u8) -> Self {
57
        // Set bits 4 and 5 indicates direction, if bit 4 is set then pixel order right -> left,
58
        // when bit 5 is set it indicates rows top -> bottom direction.
59
        // Sources:
60
        // https://en.wikipedia.org/wiki/Truevision_TGA ; Image specification (field 5)
61
489
        if value & (1u8 << 4) == 0 {
62
            // Left -> Right
63
188
            if value & (1u8 << 5) == 0 {
64
170
                TgaOrientation::BottomLeft
65
            } else {
66
18
                TgaOrientation::TopLeft
67
            }
68
        } else {
69
            // Right -> Left
70
301
            if value & (1u8 << 5) == 0 {
71
280
                TgaOrientation::BottomRight
72
            } else {
73
21
                TgaOrientation::TopRight
74
            }
75
        }
76
489
    }
77
}
78
79
/// This contains the nearest integers to the rational numbers
80
/// `(255 x) / 31`, for each `x` between 0 and 31, inclusive.
81
static LOOKUP_TABLE_5_BIT_TO_8_BIT: [u8; 32] = [
82
    0, 8, 16, 25, 33, 41, 49, 58, 66, 74, 82, 90, 99, 107, 115, 123, 132, 140, 148, 156, 165, 173,
83
    181, 189, 197, 206, 214, 222, 230, 239, 247, 255,
84
];
85
86
/// Convert TGA's 15/16-bit pixel format to its 24 bit pixel format
87
3.10M
fn expand_rgb15_to_rgb24(data: [u8; 2]) -> [u8; 3] {
88
3.10M
    let val = u16::from_le_bytes(data);
89
3.10M
    [
90
3.10M
        LOOKUP_TABLE_5_BIT_TO_8_BIT[(val & 0b11111) as usize],
91
3.10M
        LOOKUP_TABLE_5_BIT_TO_8_BIT[((val >> 5) & 0b11111) as usize],
92
3.10M
        LOOKUP_TABLE_5_BIT_TO_8_BIT[((val >> 10) & 0b11111) as usize],
93
3.10M
    ]
94
3.10M
}
95
96
impl<R: Read> TgaDecoder<R> {
97
    /// Create a new decoder that decodes from the stream `r`
98
1.03k
    pub fn new(mut r: R) -> ImageResult<TgaDecoder<R>> {
99
        // Read header
100
1.03k
        let header = Header::from_reader(&mut r)?;
101
1.00k
        let image_type = ImageType::new(header.image_type);
102
1.00k
        let width = header.image_width as usize;
103
1.00k
        let height = header.image_height as usize;
104
1.00k
        let raw_bytes_per_pixel = (header.pixel_depth as usize).div_ceil(8);
105
1.00k
        let num_attrib_bits = header.image_desc & ALPHA_BIT_MASK;
106
107
1.00k
        if width == 0 || height == 0 {
108
7
            return Err(ImageError::Decoding(DecodingError::new(
109
7
                ImageFormat::Tga.into(),
110
7
                "Invalid empty image",
111
7
            )));
112
994
        }
113
114
994
        if image_type.is_color_mapped() {
115
509
            if header.map_type != 1 {
116
8
                return Err(ImageError::Decoding(DecodingError::new(
117
8
                    ImageFormat::Tga.into(),
118
8
                    "Color map type must be 1 for color mapped images",
119
8
                )));
120
501
            } else if ![8, 16].contains(&header.pixel_depth) {
121
5
                return Err(ImageError::Decoding(DecodingError::new(
122
5
                    ImageFormat::Tga.into(),
123
5
                    "Color map must use 1 or 2 byte indexes",
124
5
                )));
125
496
            } else if header.pixel_depth > header.map_entry_size {
126
5
                return Err(ImageError::Unsupported(
127
5
                    UnsupportedError::from_format_and_kind(
128
5
                        ImageFormat::Tga.into(),
129
5
                        UnsupportedErrorKind::GenericFeature(
130
5
                            "Indices larger than pixel values".into(),
131
5
                        ),
132
5
                    ),
133
5
                ));
134
491
            }
135
485
        }
136
137
        // Compute output pixel depth
138
976
        let total_pixel_bits = if image_type.is_color_mapped() {
139
491
            header.map_entry_size
140
        } else {
141
485
            header.pixel_depth
142
        };
143
976
        let num_other_bits = total_pixel_bits
144
976
            .checked_sub(num_attrib_bits)
145
976
            .ok_or_else(|| {
146
2
                ImageError::Decoding(DecodingError::new(
147
2
                    ImageFormat::Tga.into(),
148
2
                    "More alpha bits than pixel bits",
149
2
                ))
150
2
            })?;
151
152
        // Determine color type
153
        let color_type;
154
974
        let mut original_color_type = None;
155
974
        match (num_attrib_bits, num_other_bits, image_type.is_color()) {
156
            // really, the encoding is BGR and BGRA, this is fixed up with
157
            // `TgaDecoder::reverse_encoding`.
158
165
            (0, 32, true) => color_type = ColorType::Rgba8,
159
34
            (8, 24, true) => color_type = ColorType::Rgba8,
160
178
            (0, 24, true) => color_type = ColorType::Rgb8,
161
307
            (1, 15, true) | (0, 15, true) | (0, 16, true) => {
162
307
                // the 'A' bit for 5-bit-per-primary images is an 'attribute'
163
307
                // bit, and cannot safely be interpreted as an alpha channel.
164
307
                // (It may contain all zero values or a pattern unrelated to the image.)
165
307
                color_type = ColorType::Rgb8;
166
307
                original_color_type = Some(ExtendedColorType::Rgb5x1);
167
307
            }
168
19
            (8, 8, false) => color_type = ColorType::La8,
169
172
            (0, 8, false) => color_type = ColorType::L8,
170
62
            (8, 0, false) => {
171
62
                // alpha-only image is treated as L8
172
62
                color_type = ColorType::L8;
173
62
                original_color_type = Some(ExtendedColorType::A8);
174
62
            }
175
            _ => {
176
37
                return Err(ImageError::Unsupported(
177
37
                    UnsupportedError::from_format_and_kind(
178
37
                        ImageFormat::Tga.into(),
179
37
                        UnsupportedErrorKind::Color(ExtendedColorType::Unknown(header.pixel_depth)),
180
37
                    ),
181
37
                ))
182
            }
183
        }
184
185
        // TODO: validate the rest of the fields in the header.
186
187
        // Read image ID (and ignore it)
188
937
        let mut tmp = [0u8; 256];
189
937
        r.read_exact(&mut tmp[0..header.id_length as usize])?;
190
191
        // Read color map
192
929
        let mut color_map = None;
193
929
        if header.map_type == 1 {
194
560
            if ![15, 16, 24, 32].contains(&header.map_entry_size) {
195
7
                return Err(ImageError::Unsupported(
196
7
                    UnsupportedError::from_format_and_kind(
197
7
                        ImageFormat::Tga.into(),
198
7
                        UnsupportedErrorKind::GenericFeature(
199
7
                            "Unsupported color map entry size".into(),
200
7
                        ),
201
7
                    ),
202
7
                ));
203
553
            }
204
553
            let mut entry_size = (header.map_entry_size as usize).div_ceil(8);
205
206
553
            let mut bytes = Vec::new();
207
553
            r.read_exact_vec(&mut bytes, entry_size * header.map_length as usize)?;
208
209
            // Color maps are technically allowed in non-color-mapped images, so check that we
210
            // actually need the color map before storing it.
211
458
            if image_type.is_color_mapped() {
212
                // Pre-expand 5-bit-per-primary values to simplify later decoding
213
414
                if [15, 16].contains(&header.map_entry_size) {
214
188
                    let mut expanded = Vec::new();
215
678k
                    for &entry in bytes.as_chunks::<2>().0.iter() {
216
678k
                        expanded.extend_from_slice(&expand_rgb15_to_rgb24(entry));
217
678k
                    }
218
188
                    bytes = expanded;
219
188
                    entry_size = 3;
220
226
                }
221
222
414
                color_map = Some(ColorMap {
223
414
                    entry_size,
224
414
                    start_offset: header.map_origin as usize,
225
414
                    bytes,
226
414
                });
227
44
            }
228
369
        }
229
230
827
        Ok(TgaDecoder {
231
827
            r,
232
827
233
827
            width,
234
827
            height,
235
827
            raw_bytes_per_pixel,
236
827
237
827
            image_type,
238
827
            color_type,
239
827
            original_color_type,
240
827
241
827
            header,
242
827
            color_map,
243
827
        })
244
1.03k
    }
245
246
    /// Reads a run length encoded data for given number of bytes
247
557
    fn read_encoded_data(&mut self, buf: &mut [u8]) -> io::Result<()> {
248
557
        assert!(self.raw_bytes_per_pixel <= 4);
249
557
        let mut repeat_buf = [0; 4];
250
557
        let repeat_buf = &mut repeat_buf[..self.raw_bytes_per_pixel];
251
252
557
        let mut index = 0;
253
582k
        while index < buf.len() {
254
582k
            let run_packet = self.r.read_u8()?;
255
            // If the highest bit in `run_packet` is set, then we repeat pixels
256
            //
257
            // Note: the TGA format adds 1 to both counts because having a count
258
            // of 0 would be pointless.
259
582k
            if (run_packet & 0x80) != 0 {
260
                // high bit set, so we will repeat the data
261
388k
                let repeat_count = ((run_packet & !0x80) + 1) as usize;
262
388k
                self.r.read_exact(repeat_buf)?;
263
264
45.9M
                for chunk in buf[index..]
265
388k
                    .chunks_exact_mut(self.raw_bytes_per_pixel)
266
388k
                    .take(repeat_count)
267
45.9M
                {
268
45.9M
                    chunk.copy_from_slice(repeat_buf);
269
45.9M
                }
270
388k
                index += repeat_count * self.raw_bytes_per_pixel;
271
            } else {
272
                // not set, so `run_packet+1` is the number of non-encoded pixels
273
194k
                let num_raw_bytes =
274
194k
                    ((run_packet + 1) as usize * self.raw_bytes_per_pixel).min(buf.len() - index);
275
276
194k
                self.r.read_exact(&mut buf[index..][..num_raw_bytes])?;
277
194k
                index += num_raw_bytes;
278
            }
279
        }
280
281
363
        Ok(())
282
557
    }
283
284
    /// Expands indices into its mapped color
285
252
    fn expand_color_map(
286
252
        &self,
287
252
        input: &[u8],
288
252
        output: &mut [u8],
289
252
        color_map: &ColorMap,
290
252
    ) -> ImageResult<()> {
291
252
        if self.raw_bytes_per_pixel == 1 {
292
1.88M
            for (&index, chunk) in input
293
162
                .iter()
294
162
                .zip(output.chunks_exact_mut(color_map.entry_size))
295
            {
296
1.88M
                if let Some(color) = color_map.get(index as usize) {
297
1.88M
                    chunk.copy_from_slice(color);
298
1.88M
                } else {
299
130
                    return Err(ImageError::Decoding(DecodingError::new(
300
130
                        ImageFormat::Tga.into(),
301
130
                        "Invalid color map index",
302
130
                    )));
303
                }
304
            }
305
90
        } else if self.raw_bytes_per_pixel == 2 {
306
90
            let input_chunks = input.as_chunks::<2>().0.iter();
307
8.70k
            for (&index, chunk) in input_chunks.zip(output.chunks_exact_mut(color_map.entry_size)) {
308
8.70k
                let index = u16::from_le_bytes(index);
309
8.70k
                if let Some(color) = color_map.get(index as usize) {
310
8.64k
                    chunk.copy_from_slice(color);
311
8.64k
                } else {
312
64
                    return Err(ImageError::Decoding(DecodingError::new(
313
64
                        ImageFormat::Tga.into(),
314
64
                        "Invalid color map index",
315
64
                    )));
316
                }
317
            }
318
        } else {
319
0
            unreachable!("Supported bytes_per_pixel values are checked in TgaDecoder::new");
320
        }
321
322
58
        Ok(())
323
252
    }
324
325
    /// Reverse from BGR encoding to RGB encoding
326
    ///
327
    /// TGA files are stored in the BGRA encoding. This function swaps
328
    /// the blue and red bytes in the `pixels` array.
329
295
    fn reverse_encoding_in_output(&mut self, pixels: &mut [u8]) {
330
        // We only need to reverse the encoding of color images
331
295
        match self.color_type {
332
            ColorType::Rgb8 | ColorType::Rgba8 => {
333
7.30M
                for chunk in pixels.chunks_exact_mut(self.color_type.bytes_per_pixel().into()) {
334
7.30M
                    chunk.swap(0, 2);
335
7.30M
                }
336
            }
337
105
            _ => {}
338
        }
339
295
    }
340
341
    /// Change image orientation depending on the flags set
342
489
    fn fixup_orientation(&mut self, pixels: &mut [u8]) {
343
        // The below code assumes that the image is non-empty and will crash
344
        // otherwise. The constructor *currently* disallows empty images, so
345
        // this is method does not panic.
346
489
        debug_assert!(self.width > 0 && self.height > 0);
347
348
489
        let orientation = TgaOrientation::from_image_desc_byte(self.header.image_desc);
349
350
        // Flip image if bottom->top direction
351
489
        if (orientation == TgaOrientation::BottomLeft || orientation == TgaOrientation::BottomRight)
352
450
            && self.height > 1
353
        {
354
267
            let row_stride = self.width * self.raw_bytes_per_pixel;
355
356
267
            let (left_part, right_part) = pixels.split_at_mut(self.height / 2 * row_stride);
357
358
522k
            for (src, dst) in left_part
359
267
                .chunks_exact_mut(row_stride)
360
267
                .zip(right_part.chunks_exact_mut(row_stride).rev())
361
            {
362
19.3M
                for (src, dst) in src.iter_mut().zip(dst.iter_mut()) {
363
19.3M
                    std::mem::swap(src, dst);
364
19.3M
                }
365
            }
366
222
        }
367
368
        // Flop image if right->left direction
369
489
        if (orientation == TgaOrientation::BottomRight || orientation == TgaOrientation::TopRight)
370
301
            && self.width > 1
371
        {
372
398k
            for row in pixels.chunks_exact_mut(self.width * self.raw_bytes_per_pixel) {
373
398k
                let (left_part, right_part) =
374
398k
                    row.split_at_mut(self.width / 2 * self.raw_bytes_per_pixel);
375
7.12M
                for (src, dst) in left_part
376
398k
                    .chunks_exact_mut(self.raw_bytes_per_pixel)
377
398k
                    .zip(right_part.chunks_exact_mut(self.raw_bytes_per_pixel).rev())
378
                {
379
10.6M
                    for (src, dst) in src.iter_mut().zip(dst.iter_mut()) {
380
10.6M
                        std::mem::swap(dst, src);
381
10.6M
                    }
382
                }
383
            }
384
248
        }
385
489
    }
386
}
387
388
impl<R: Read> ImageDecoder for TgaDecoder<R> {
389
2.35k
    fn dimensions(&self) -> (u32, u32) {
390
2.35k
        (self.width as u32, self.height as u32)
391
2.35k
    }
392
393
2.35k
    fn color_type(&self) -> ColorType {
394
2.35k
        self.color_type
395
2.35k
    }
396
397
0
    fn original_color_type(&self) -> ExtendedColorType {
398
0
        self.original_color_type
399
0
            .unwrap_or_else(|| self.color_type().into())
400
0
    }
401
402
764
    fn read_image(mut self, buf: &mut [u8]) -> ImageResult<()> {
403
764
        assert_eq!(u64::try_from(buf.len()), Ok(self.total_bytes()));
404
405
        // Decode the raw data
406
        //
407
        // We currently assume that the indices take less space than the
408
        // pixels they encode, so it is safe to read the raw data into `buf`.
409
764
        if self.raw_bytes_per_pixel > self.color_type.bytes_per_pixel().into() {
410
0
            return Err(ImageError::Unsupported(
411
0
                UnsupportedError::from_format_and_kind(
412
0
                    ImageFormat::Tga.into(),
413
0
                    UnsupportedErrorKind::GenericFeature(
414
0
                        "Color-mapped images with indices wider than color are not supported"
415
0
                            .into(),
416
0
                    ),
417
0
                ),
418
0
            ));
419
764
        }
420
764
        let num_raw_bytes = self.width * self.height * self.raw_bytes_per_pixel;
421
764
        debug_assert!(num_raw_bytes <= buf.len());
422
423
764
        if self.image_type.is_encoded() {
424
557
            self.read_encoded_data(&mut buf[..num_raw_bytes])?;
425
        } else {
426
207
            self.r.read_exact(&mut buf[..num_raw_bytes])?;
427
        }
428
429
489
        self.fixup_orientation(&mut buf[..num_raw_bytes]);
430
431
        // Expand the indices using the color map if necessary
432
489
        if let Some(ref color_map) = self.color_map {
433
            // This allocation could be avoided by expanding each row (or block of pixels) as it is
434
            // read, or by doing the color map expansion in-place. But those may be more effort than
435
            // it is worth.
436
252
            let mut rawbuf = vec_try_with_capacity(num_raw_bytes)?;
437
252
            rawbuf.extend_from_slice(&buf[..num_raw_bytes]);
438
439
252
            self.expand_color_map(&rawbuf, buf, color_map)?;
440
237
        } else if self.original_color_type == Some(ExtendedColorType::Rgb5x1) {
441
            // Expand the 15-bit to 24-bit representation for non-color-mapped images;
442
            // the expansion for color-mapped 15-bit images was already done in the color map
443
53
            let mut rawbuf = vec_try_with_capacity(num_raw_bytes)?;
444
53
            rawbuf.extend_from_slice(&buf[..num_raw_bytes]);
445
446
53
            let rawbuf_chunks = rawbuf.as_chunks::<2>().0.iter();
447
53
            let buf_chunks = buf.as_chunks_mut::<3>().0.iter_mut();
448
2.42M
            for (&src, dst) in rawbuf_chunks.zip(buf_chunks) {
449
2.42M
                *dst = expand_rgb15_to_rgb24(src);
450
2.42M
            }
451
184
        }
452
453
295
        self.reverse_encoding_in_output(buf);
454
455
295
        Ok(())
456
764
    }
457
458
0
    fn read_image_boxed(self: Box<Self>, buf: &mut [u8]) -> ImageResult<()> {
459
0
        (*self).read_image(buf)
460
0
    }
461
}