Coverage Report

Created: 2026-06-18 07:57

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