Skip to content

Commit 7594a67

Browse files
committed
tga: decode 5-bit-per-primary images
This is very loosely based on a patch by red-001 <[email protected]>.
1 parent 1ed88e4 commit 7594a67

File tree

11 files changed

+87
-53
lines changed

11 files changed

+87
-53
lines changed

src/codecs/tga/decoder.rs

Lines changed: 83 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,23 @@ impl TgaOrientation {
7676
}
7777
}
7878

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+
fn expand_rgb15_to_rgb24(data: [u8; 2]) -> [u8; 3] {
88+
let val = u16::from_le_bytes(data);
89+
[
90+
LOOKUP_TABLE_5_BIT_TO_8_BIT[(val & 0b11111) as usize],
91+
LOOKUP_TABLE_5_BIT_TO_8_BIT[((val >> 5) & 0b11111) as usize],
92+
LOOKUP_TABLE_5_BIT_TO_8_BIT[((val >> 10) & 0b11111) as usize],
93+
]
94+
}
95+
7996
impl<R: Read> TgaDecoder<R> {
8097
/// Create a new decoder that decodes from the stream `r`
8198
pub fn new(mut r: R) -> ImageResult<TgaDecoder<R>> {
@@ -85,7 +102,7 @@ impl<R: Read> TgaDecoder<R> {
85102
let width = header.image_width as usize;
86103
let height = header.image_height as usize;
87104
let raw_bytes_per_pixel = (header.pixel_depth as usize).div_ceil(8);
88-
let num_alpha_bits = header.image_desc & ALPHA_BIT_MASK;
105+
let num_attrib_bits = header.image_desc & ALPHA_BIT_MASK;
89106

90107
if width == 0 || height == 0 {
91108
return Err(ImageError::Decoding(DecodingError::new(
@@ -94,15 +111,6 @@ impl<R: Read> TgaDecoder<R> {
94111
)));
95112
}
96113

97-
// Validate header
98-
if ![8, 16, 24, 32].contains(&header.pixel_depth) || ![0, 8].contains(&num_alpha_bits) {
99-
return Err(ImageError::Unsupported(
100-
UnsupportedError::from_format_and_kind(
101-
ImageFormat::Tga.into(),
102-
UnsupportedErrorKind::Color(ExtendedColorType::Unknown(header.pixel_depth)),
103-
),
104-
));
105-
}
106114
if image_type.is_color_mapped() {
107115
if header.map_type != 1 {
108116
return Err(ImageError::Decoding(DecodingError::new(
@@ -126,49 +134,14 @@ impl<R: Read> TgaDecoder<R> {
126134
}
127135
}
128136

129-
// TODO: validate the rest of the fields in the header.
130-
131-
// Read image ID (and ignore it)
132-
let mut tmp = [0u8; 256];
133-
r.read_exact(&mut tmp[0..header.id_length as usize])?;
134-
135-
// Read color map
136-
let mut color_map = None;
137-
if header.map_type == 1 {
138-
let entry_size = (header.map_entry_size as usize).div_ceil(8);
139-
if ![2, 3, 4].contains(&entry_size) {
140-
return Err(ImageError::Unsupported(
141-
UnsupportedError::from_format_and_kind(
142-
ImageFormat::Tga.into(),
143-
UnsupportedErrorKind::GenericFeature(
144-
"Unsupported color map entry size".into(),
145-
),
146-
),
147-
));
148-
}
149-
150-
let mut bytes = Vec::new();
151-
r.read_exact_vec(&mut bytes, entry_size * header.map_length as usize)?;
152-
153-
// Color maps are technically allowed in non-color-mapped images, so check that we
154-
// actually need the color map before storing it.
155-
if image_type.is_color_mapped() {
156-
color_map = Some(ColorMap {
157-
entry_size,
158-
start_offset: header.map_origin as usize,
159-
bytes,
160-
});
161-
}
162-
}
163-
164137
// Compute output pixel depth
165-
let total_pixel_bits = if header.map_type == 1 {
138+
let total_pixel_bits = if image_type.is_color_mapped() {
166139
header.map_entry_size
167140
} else {
168141
header.pixel_depth
169142
};
170143
let num_other_bits = total_pixel_bits
171-
.checked_sub(num_alpha_bits)
144+
.checked_sub(num_attrib_bits)
172145
.ok_or_else(|| {
173146
ImageError::Decoding(DecodingError::new(
174147
ImageFormat::Tga.into(),
@@ -179,12 +152,19 @@ impl<R: Read> TgaDecoder<R> {
179152
// Determine color type
180153
let color_type;
181154
let mut original_color_type = None;
182-
match (num_alpha_bits, num_other_bits, image_type.is_color()) {
155+
match (num_attrib_bits, num_other_bits, image_type.is_color()) {
183156
// really, the encoding is BGR and BGRA, this is fixed up with
184157
// `TgaDecoder::reverse_encoding`.
185158
(0, 32, true) => color_type = ColorType::Rgba8,
186159
(8, 24, true) => color_type = ColorType::Rgba8,
187160
(0, 24, true) => color_type = ColorType::Rgb8,
161+
(1, 15, true) | (0, 15, true) | (0, 16, true) => {
162+
// the 'A' bit for 5-bit-per-primary images is an 'attribute'
163+
// bit, and cannot safely be interpreted as an alpha channel.
164+
// (It may contain all zero values or a pattern unrelated to the image.)
165+
color_type = ColorType::Rgb8;
166+
original_color_type = Some(ExtendedColorType::Rgb5x1);
167+
}
188168
(8, 8, false) => color_type = ColorType::La8,
189169
(0, 8, false) => color_type = ColorType::L8,
190170
(8, 0, false) => {
@@ -202,6 +182,52 @@ impl<R: Read> TgaDecoder<R> {
202182
}
203183
}
204184

185+
// TODO: validate the rest of the fields in the header.
186+
187+
// Read image ID (and ignore it)
188+
let mut tmp = [0u8; 256];
189+
r.read_exact(&mut tmp[0..header.id_length as usize])?;
190+
191+
// Read color map
192+
let mut color_map = None;
193+
if header.map_type == 1 {
194+
if ![15, 16, 24, 32].contains(&header.map_entry_size) {
195+
return Err(ImageError::Unsupported(
196+
UnsupportedError::from_format_and_kind(
197+
ImageFormat::Tga.into(),
198+
UnsupportedErrorKind::GenericFeature(
199+
"Unsupported color map entry size".into(),
200+
),
201+
),
202+
));
203+
}
204+
let mut entry_size = (header.map_entry_size as usize).div_ceil(8);
205+
206+
let mut bytes = Vec::new();
207+
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+
if image_type.is_color_mapped() {
212+
// Pre-expand 5-bit-per-primary values to simplify later decoding
213+
if [15, 16].contains(&header.map_entry_size) {
214+
let mut expanded = Vec::new();
215+
for entry in bytes.chunks_exact(2) {
216+
expanded
217+
.extend_from_slice(&expand_rgb15_to_rgb24(entry.try_into().unwrap()));
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+
205231
Ok(TgaDecoder {
206232
r,
207233

@@ -396,6 +422,15 @@ impl<R: Read> ImageDecoder for TgaDecoder<R> {
396422
rawbuf.extend_from_slice(&buf[..num_raw_bytes]);
397423

398424
self.expand_color_map(&rawbuf, buf, color_map)?;
425+
} else if self.original_color_type == Some(ExtendedColorType::Rgb5x1) {
426+
// Expand the 15-bit to 24-bit representation for non-color-mapped images;
427+
// the expansion for color-mapped 15-bit images was already done in the color map
428+
let mut rawbuf = vec_try_with_capacity(num_raw_bytes)?;
429+
rawbuf.extend_from_slice(&buf[..num_raw_bytes]);
430+
431+
for (src, dst) in rawbuf.chunks_exact(2).zip(buf.chunks_exact_mut(3)) {
432+
dst.copy_from_slice(&expand_rgb15_to_rgb24(src.try_into().unwrap()));
433+
}
399434
}
400435

401436
self.reverse_encoding_in_output(buf);

src/codecs/tga/mod.rs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,8 @@
33
//! # Related Links
44
//! <http://googlesites.inequation.org/tgautilities>
55
6-
/// A decoder for TGA images
7-
///
8-
/// Currently this decoder does not support 8, 15 and 16 bit color images.
96
pub use self::decoder::TgaDecoder;
107

11-
//TODO add 8, 15, 16 bit color support
12-
138
pub use self::encoder::TgaEncoder;
149

1510
mod decoder;

src/color.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,8 @@ pub enum ExtendedColorType {
125125
Rgb4,
126126
/// Pixel is 4-bit RGB with an alpha channel
127127
Rgba4,
128+
/// Pixel contains 5-bit R, G and B channels packed into 2 bytes
129+
Rgb5x1,
128130
/// Pixel is 8-bit luminance
129131
L8,
130132
/// Pixel is 8-bit luminance with an alpha channel
@@ -184,6 +186,7 @@ impl ExtendedColorType {
184186
ExtendedColorType::Rgb1
185187
| ExtendedColorType::Rgb2
186188
| ExtendedColorType::Rgb4
189+
| ExtendedColorType::Rgb5x1
187190
| ExtendedColorType::Rgb8
188191
| ExtendedColorType::Rgb16
189192
| ExtendedColorType::Rgb32F
@@ -216,6 +219,7 @@ impl ExtendedColorType {
216219
ExtendedColorType::La4 => 8,
217220
ExtendedColorType::Rgb4 => 12,
218221
ExtendedColorType::Rgba4 => 16,
222+
ExtendedColorType::Rgb5x1 => 16,
219223
ExtendedColorType::L8 => 8,
220224
ExtendedColorType::La8 => 16,
221225
ExtendedColorType::Rgb8 => 24,
50 Bytes
Binary file not shown.
66 Bytes
Binary file not shown.
50 Bytes
Binary file not shown.
82 Bytes
Binary file not shown.
120 Bytes
Loading
120 Bytes
Loading
120 Bytes
Loading

0 commit comments

Comments
 (0)