Skip to content

Commit d8185b8

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 965a602 commit d8185b8

File tree

11 files changed

+85
-53
lines changed

11 files changed

+85
-53
lines changed

src/codecs/tga/decoder.rs

Lines changed: 81 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,17 +102,8 @@ 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

90-
// Validate header
91-
if ![8, 16, 24, 32].contains(&header.pixel_depth) || ![0, 8].contains(&num_alpha_bits) {
92-
return Err(ImageError::Unsupported(
93-
UnsupportedError::from_format_and_kind(
94-
ImageFormat::Tga.into(),
95-
UnsupportedErrorKind::Color(ExtendedColorType::Unknown(header.pixel_depth)),
96-
),
97-
));
98-
}
99107
if image_type.is_color_mapped() {
100108
if header.map_type != 1 {
101109
return Err(ImageError::Decoding(DecodingError::new(
@@ -119,49 +127,14 @@ impl<R: Read> TgaDecoder<R> {
119127
}
120128
}
121129

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

178+
// TODO: validate the rest of the fields in the header.
179+
180+
// Read image ID (and ignore it)
181+
let mut tmp = [0u8; 256];
182+
r.read_exact(&mut tmp[0..header.id_length as usize])?;
183+
184+
// Read color map
185+
let mut color_map = None;
186+
if header.map_type == 1 {
187+
if ![15, 16, 24, 32].contains(&header.map_entry_size) {
188+
return Err(ImageError::Unsupported(
189+
UnsupportedError::from_format_and_kind(
190+
ImageFormat::Tga.into(),
191+
UnsupportedErrorKind::GenericFeature(
192+
"Unsupported color map entry size".into(),
193+
),
194+
),
195+
));
196+
}
197+
let mut entry_size = (header.map_entry_size as usize).div_ceil(8);
198+
199+
let mut bytes = Vec::new();
200+
r.read_exact_vec(&mut bytes, entry_size * header.map_length as usize)?;
201+
202+
// Color maps are technically allowed in non-color-mapped images, so check that we
203+
// actually need the color map before storing it.
204+
if image_type.is_color_mapped() {
205+
// Pre-expand 5-bit-per-primary values to simplify later decoding
206+
if [15, 16].contains(&header.map_entry_size) {
207+
let mut expanded = Vec::new();
208+
for entry in bytes.chunks_exact(2) {
209+
expanded
210+
.extend_from_slice(&expand_rgb15_to_rgb24(entry.try_into().unwrap()));
211+
}
212+
bytes = expanded;
213+
entry_size = 3;
214+
}
215+
216+
color_map = Some(ColorMap {
217+
entry_size,
218+
start_offset: header.map_origin as usize,
219+
bytes,
220+
});
221+
}
222+
}
223+
198224
Ok(TgaDecoder {
199225
r,
200226

@@ -389,6 +415,13 @@ impl<R: Read> ImageDecoder for TgaDecoder<R> {
389415
rawbuf.extend_from_slice(&buf[..num_raw_bytes]);
390416

391417
self.expand_color_map(&rawbuf, buf, color_map)?;
418+
} else if self.original_color_type == Some(ExtendedColorType::Rgb5) {
419+
let mut rawbuf = vec_try_with_capacity(num_raw_bytes)?;
420+
rawbuf.extend_from_slice(&buf[..num_raw_bytes]);
421+
422+
for (src, dst) in rawbuf.chunks_exact(2).zip(buf.chunks_exact_mut(3)) {
423+
dst.copy_from_slice(&expand_rgb15_to_rgb24(src.try_into().unwrap()));
424+
}
392425
}
393426

394427
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
129+
Rgb5,
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::Rgb5
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::Rgb5 => 15,
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)