imagesize/formats/
tga.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
use std::io::{BufRead, Seek, SeekFrom};

use crate::{util::*, ImageResult, ImageSize};

pub fn size<R: BufRead + Seek>(reader: &mut R) -> ImageResult<ImageSize> {
    reader.seek(SeekFrom::Start(12))?;

    let width = read_u16(reader, &Endian::Little)? as usize;
    let height = read_u16(reader, &Endian::Little)? as usize;

    Ok(ImageSize { width, height })
}

pub fn matches<R: BufRead + Seek>(header: &[u8], reader: &mut R) -> bool {
    // Do a naive check first to filter out any obviously non-TGA files
    let colormap_type = header[1];
    let image_type = header[2];

    // Check the image type (byte 2) to be one of the uncompressed or RLE compressed types
    // Note: I've seen mention of types 0, 32, and 33 but have no example files so have omitted them.
    if image_type != 1
        && image_type != 2
        && image_type != 3
        && image_type != 9
        && image_type != 10
        && image_type != 11
    {
        return false;
    }

    // Check that the colormap type (byte 1) is either 0 (no colormap) or 1 (colormap present)
    // Technically 2-127 is reserved and 128-255 are usable by devs, but for simplicity we ignore them
    if colormap_type >= 2 {
        return false;
    }

    is_tga(reader, image_type, colormap_type).unwrap_or(false)
}

fn is_tga<R: BufRead + Seek>(reader: &mut R, image_type: u8, colormap_type: u8) -> ImageResult<bool> {
    // Attempt to go to footer section. This also doubles as a size check since
    // if there aren't 18 bytes available it will return an error.
    reader.seek(SeekFrom::End(-18))?;

    // Look for Version 2 TGA footer signature as it's the only concrete data to verify it's a TGA
    let mut signature = [0; 18];
    reader.read_exact(&mut signature)?;

    // If signature is found then we should be confident it's a TGA
    //
    // We do not reset the seek here because size methods should
    // be seeking themselves as a first step anyway.
    if &signature == b"TRUEVISION-XFILE.\0" {
        return Ok(true);
    }

    // Now we're into heuristic territory. 
    // With no footer I don't believe there is a 100% way to verify whether given bytes 
    // are a TGA or not. To get around this we add a few corroborating byte checks and
    // if they make up a valid TGA configuration we assume that it's a TGA.

    // If image type is color mapped, then color map type must be set to 1
    if (image_type == 1 || image_type == 9) && colormap_type != 1 {
        return Ok(false);
    }

    // Start reading the header information
    reader.seek(SeekFrom::Start(3))?;

    let colormap_offset = read_u32(reader, &Endian::Little)?;
    let colormap_size = read_u8(reader)?;

    // If there is no color map then assume that origin, length, and entry size must be 0
    if colormap_type == 0 {
        if colormap_offset != 0 {
            return Ok(false);
        }

        if colormap_size != 0 {
            return Ok(false);
        }
    }

    // Assume color map sizes must be a multiple of 8
    if colormap_type == 1 && 
       (colormap_size != 0 && 
        colormap_size != 8 && 
        colormap_size != 16 && 
        colormap_size != 24 && 
        colormap_size != 32)
    {
        return Ok(false);
    }

    reader.seek(SeekFrom::Start(16))?;
    let pixel_size = read_u8(reader)?;
    let descriptor = read_u8(reader)?;
    let alpha_bits = descriptor & 0x0F;

    // Reserved bit, must be set to 0
    if descriptor & 0x10 != 0 {
        return Ok(false);
    }

    // Assume pixel size must be a multiple of 8
    if pixel_size != 8 && pixel_size != 16 && pixel_size != 24 && pixel_size != 32 {
        return Ok(false);
    }

    // Verify that the alpha bits value makes sense given pixel size
    //
    // 8 and 24 bits have no alpha
    if (pixel_size == 8 || pixel_size == 24) && alpha_bits != 0 {
        return Ok(false);
    }

    // 16 bits can either have 0 or 1 bits of alpha
    if pixel_size == 16 && alpha_bits >= 2 {
        return Ok(false);
    }

    // 32 bits must have 8 bits of alpha, although I've seen one with 0?
    if pixel_size == 32 && (alpha_bits != 8 && alpha_bits != 0) {
        return Ok(false);
    }

    Ok(true)
}