Skip to main content

ai_image/codecs/tga/
decoder.rs

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