use alloc::vec;
use alloc::vec::Vec;
use enough::Stop;
use crate::error::BitmapError;
use crate::pixel::PixelLayout;
#[derive(Clone, Debug)]
#[allow(dead_code)] pub(crate) struct TgaHeader {
pub id_length: u8,
pub color_map_type: u8,
pub image_type: u8,
pub color_map_start: u16,
pub color_map_length: u16,
pub color_map_depth: u8,
pub x_origin: u16,
pub y_origin: u16,
pub width: u16,
pub height: u16,
pub pixel_depth: u8,
pub descriptor: u8,
}
impl TgaHeader {
pub(crate) fn alpha_bits(&self) -> u8 {
self.descriptor & 0x0F
}
fn is_top_to_bottom(&self) -> bool {
self.descriptor & 0x20 != 0
}
fn is_right_to_left(&self) -> bool {
self.descriptor & 0x10 != 0
}
fn is_rle(&self) -> bool {
matches!(self.image_type, 9..=11)
}
pub(crate) fn is_color_mapped(&self) -> bool {
matches!(self.image_type, 1 | 9)
}
pub(crate) fn is_grayscale(&self) -> bool {
matches!(self.image_type, 3 | 11)
}
}
pub(crate) fn parse_header(data: &[u8]) -> Result<TgaHeader, BitmapError> {
if data.len() < 18 {
return Err(BitmapError::UnexpectedEof);
}
let header = TgaHeader {
id_length: data[0],
color_map_type: data[1],
image_type: data[2],
color_map_start: u16::from_le_bytes([data[3], data[4]]),
color_map_length: u16::from_le_bytes([data[5], data[6]]),
color_map_depth: data[7],
x_origin: u16::from_le_bytes([data[8], data[9]]),
y_origin: u16::from_le_bytes([data[10], data[11]]),
width: u16::from_le_bytes([data[12], data[13]]),
height: u16::from_le_bytes([data[14], data[15]]),
pixel_depth: data[16],
descriptor: data[17],
};
if !matches!(header.image_type, 1 | 2 | 3 | 9 | 10 | 11) {
return Err(BitmapError::UnsupportedVariant(alloc::format!(
"TGA image type {} is not supported",
header.image_type
)));
}
if header.width == 0 {
return Err(BitmapError::InvalidHeader("TGA width is zero".into()));
}
if header.height == 0 {
return Err(BitmapError::InvalidHeader("TGA height is zero".into()));
}
if header.color_map_type > 1 {
return Err(BitmapError::InvalidHeader(alloc::format!(
"TGA color_map_type {} is invalid (must be 0 or 1)",
header.color_map_type
)));
}
if header.is_color_mapped() && header.color_map_type != 1 {
return Err(BitmapError::InvalidHeader(
"TGA color-mapped image must have color_map_type=1".into(),
));
}
match header.image_type {
1 | 9 => {
if header.pixel_depth != 8 {
return Err(BitmapError::UnsupportedVariant(alloc::format!(
"TGA color-mapped pixel_depth {} not supported (only 8-bit indices)",
header.pixel_depth
)));
}
if !matches!(header.color_map_depth, 15 | 16 | 24 | 32) {
return Err(BitmapError::UnsupportedVariant(alloc::format!(
"TGA color_map_depth {} not supported (must be 15, 16, 24, or 32)",
header.color_map_depth
)));
}
}
2 | 10 => {
if !matches!(header.pixel_depth, 15 | 16 | 24 | 32) {
return Err(BitmapError::UnsupportedVariant(alloc::format!(
"TGA truecolor pixel_depth {} not supported (must be 15, 16, 24, or 32)",
header.pixel_depth
)));
}
}
3 | 11 => {
if header.pixel_depth != 8 {
return Err(BitmapError::UnsupportedVariant(alloc::format!(
"TGA grayscale pixel_depth {} not supported (only 8-bit)",
header.pixel_depth
)));
}
}
_ => unreachable!(), }
Ok(header)
}
pub(crate) fn decode_pixels(
data: &[u8],
header: &TgaHeader,
stop: &dyn Stop,
) -> Result<(Vec<u8>, PixelLayout), BitmapError> {
let w = header.width as usize;
let h = header.height as usize;
let pixel_data_offset = 18 + header.id_length as usize;
let (color_map, color_map_end) = if header.color_map_type == 1 {
let entry_bytes = match header.color_map_depth {
15 | 16 => 2,
24 => 3,
32 => 4,
_ => {
return Err(BitmapError::UnsupportedVariant(alloc::format!(
"TGA color_map_depth {} not supported",
header.color_map_depth
)));
}
};
let map_size = (header.color_map_length as usize)
.checked_mul(entry_bytes)
.ok_or_else(|| BitmapError::InvalidHeader("color map size overflow".into()))?;
let map_start = pixel_data_offset;
let map_end = map_start
.checked_add(map_size)
.ok_or(BitmapError::UnexpectedEof)?;
if data.len() < map_end {
return Err(BitmapError::UnexpectedEof);
}
(Some(&data[map_start..map_end]), map_end)
} else {
(None, pixel_data_offset)
};
let pixel_data = data
.get(color_map_end..)
.ok_or(BitmapError::UnexpectedEof)?;
let (layout, out_channels) = if header.is_grayscale() {
(PixelLayout::Gray8, 1)
} else if header.pixel_depth == 32
|| (header.is_color_mapped() && header.color_map_depth == 32)
|| header.alpha_bits() > 0
{
(PixelLayout::Rgba8, 4)
} else {
(PixelLayout::Rgb8, 3)
};
let pixel_count = w.checked_mul(h).ok_or(BitmapError::DimensionsTooLarge {
width: header.width as u32,
height: header.height as u32,
})?;
let out_size =
pixel_count
.checked_mul(out_channels)
.ok_or(BitmapError::DimensionsTooLarge {
width: header.width as u32,
height: header.height as u32,
})?;
let mut out = vec![0u8; out_size];
let src_bpp: usize = match header.pixel_depth {
8 => 1,
15 | 16 => 2,
24 => 3,
32 => 4,
_ => unreachable!(), };
if header.is_rle() {
decode_rle(
pixel_data,
&mut out,
header,
src_bpp,
out_channels,
color_map,
stop,
)?;
} else {
decode_raw(
pixel_data,
&mut out,
header,
src_bpp,
out_channels,
color_map,
stop,
)?;
}
if header.is_right_to_left() {
flip_horizontal(&mut out, w, h, out_channels);
}
if !header.is_top_to_bottom() {
flip_rows(&mut out, w, h, out_channels);
}
Ok((out, layout))
}
fn decode_raw(
pixel_data: &[u8],
out: &mut [u8],
header: &TgaHeader,
src_bpp: usize,
out_channels: usize,
color_map: Option<&[u8]>,
stop: &dyn Stop,
) -> Result<(), BitmapError> {
let w = header.width as usize;
let h = header.height as usize;
let row_bytes = w
.checked_mul(src_bpp)
.ok_or(BitmapError::DimensionsTooLarge {
width: header.width as u32,
height: header.height as u32,
})?;
let total_src_bytes = row_bytes
.checked_mul(h)
.ok_or(BitmapError::DimensionsTooLarge {
width: header.width as u32,
height: header.height as u32,
})?;
if pixel_data.len() < total_src_bytes {
return Err(BitmapError::UnexpectedEof);
}
if !header.is_color_mapped()
&& !header.is_grayscale()
&& src_bpp == out_channels
&& matches!(header.pixel_depth, 24 | 32)
{
out[..total_src_bytes].copy_from_slice(&pixel_data[..total_src_bytes]);
if out_channels == 3 {
#[cfg(feature = "simd")]
{
let _ = garb::bytes::rgb_to_bgr_inplace(out);
}
#[cfg(not(feature = "simd"))]
for pixel in out.chunks_exact_mut(3) {
pixel.swap(0, 2);
}
} else {
#[cfg(feature = "simd")]
{
let _ = garb::bytes::rgba_to_bgra_inplace(out);
}
#[cfg(not(feature = "simd"))]
for pixel in out.chunks_exact_mut(4) {
pixel.swap(0, 2);
}
}
return Ok(());
}
for y in 0..h {
if y % 16 == 0 {
stop.check()?;
}
let src_row = &pixel_data[y * row_bytes..(y + 1) * row_bytes];
let dst_row_start = y * w * out_channels;
for x in 0..w {
let src = &src_row[x * src_bpp..(x + 1) * src_bpp];
let dst_off = dst_row_start + x * out_channels;
convert_pixel(
src,
&mut out[dst_off..dst_off + out_channels],
header,
color_map,
)?;
}
}
Ok(())
}
fn decode_rle(
pixel_data: &[u8],
out: &mut [u8],
header: &TgaHeader,
src_bpp: usize,
out_channels: usize,
color_map: Option<&[u8]>,
stop: &dyn Stop,
) -> Result<(), BitmapError> {
let w = header.width as usize;
let h = header.height as usize;
let total_pixels = w * h;
let mut src_pos = 0;
let mut pixel_idx = 0;
while pixel_idx < total_pixels {
if pixel_idx % (w * 16) == 0 {
stop.check()?;
}
if src_pos >= pixel_data.len() {
return Err(BitmapError::UnexpectedEof);
}
let packet_header = pixel_data[src_pos];
src_pos += 1;
let run_count = (packet_header & 0x7F) as usize + 1;
let is_rle_packet = packet_header & 0x80 != 0;
if pixel_idx + run_count > total_pixels {
return Err(BitmapError::InvalidData(
"TGA RLE packet exceeds image bounds".into(),
));
}
if is_rle_packet {
if src_pos + src_bpp > pixel_data.len() {
return Err(BitmapError::UnexpectedEof);
}
let src = &pixel_data[src_pos..src_pos + src_bpp];
src_pos += src_bpp;
let mut converted = [0u8; 4];
convert_pixel(src, &mut converted[..out_channels], header, color_map)?;
for _ in 0..run_count {
let dst_off = pixel_idx * out_channels;
out[dst_off..dst_off + out_channels].copy_from_slice(&converted[..out_channels]);
pixel_idx += 1;
}
} else {
let needed = run_count
.checked_mul(src_bpp)
.ok_or(BitmapError::UnexpectedEof)?;
if src_pos + needed > pixel_data.len() {
return Err(BitmapError::UnexpectedEof);
}
for _ in 0..run_count {
let src = &pixel_data[src_pos..src_pos + src_bpp];
src_pos += src_bpp;
let dst_off = pixel_idx * out_channels;
convert_pixel(
src,
&mut out[dst_off..dst_off + out_channels],
header,
color_map,
)?;
pixel_idx += 1;
}
}
}
Ok(())
}
fn convert_pixel(
src: &[u8],
dst: &mut [u8],
header: &TgaHeader,
color_map: Option<&[u8]>,
) -> Result<(), BitmapError> {
if header.is_color_mapped() {
let index = src[0] as usize;
let map = color_map.ok_or_else(|| {
BitmapError::InvalidData("color-mapped image has no color map".into())
})?;
let adjusted_index = index
.checked_sub(header.color_map_start as usize)
.ok_or_else(|| {
BitmapError::InvalidData(alloc::format!(
"palette index {index} is below color_map_start {}",
header.color_map_start
))
})?;
let entry_bytes: usize = match header.color_map_depth {
15 | 16 => 2,
24 => 3,
32 => 4,
_ => unreachable!(),
};
let entry_offset = adjusted_index
.checked_mul(entry_bytes)
.ok_or(BitmapError::UnexpectedEof)?;
if entry_offset + entry_bytes > map.len() {
return Err(BitmapError::InvalidData(alloc::format!(
"palette index {index} out of range"
)));
}
let entry = &map[entry_offset..entry_offset + entry_bytes];
convert_color(entry, dst, header.color_map_depth);
} else if header.is_grayscale() {
dst[0] = src[0];
} else {
convert_color(src, dst, header.pixel_depth);
}
Ok(())
}
fn convert_color(src: &[u8], dst: &mut [u8], depth: u8) {
match depth {
15 | 16 => {
let val = u16::from_le_bytes([src[0], src[1]]);
let r5 = ((val >> 10) & 0x1F) as u8;
let g5 = ((val >> 5) & 0x1F) as u8;
let b5 = (val & 0x1F) as u8;
dst[0] = ((r5 as u16 * 255 + 15) / 31) as u8;
dst[1] = ((g5 as u16 * 255 + 15) / 31) as u8;
dst[2] = ((b5 as u16 * 255 + 15) / 31) as u8;
if dst.len() >= 4 {
dst[3] = if val & 0x8000 != 0 { 255 } else { 0 };
}
}
24 => {
dst[0] = src[2];
dst[1] = src[1];
dst[2] = src[0];
}
32 => {
dst[0] = src[2];
dst[1] = src[1];
dst[2] = src[0];
if dst.len() >= 4 {
dst[3] = src[3];
}
}
_ => unreachable!(),
}
}
fn flip_rows(buf: &mut [u8], w: usize, h: usize, channels: usize) {
let row_bytes = w * channels;
let mut top = 0;
let mut bot = (h - 1) * row_bytes;
while top < bot {
for i in 0..row_bytes {
buf.swap(top + i, bot + i);
}
top += row_bytes;
bot -= row_bytes;
}
}
fn flip_horizontal(buf: &mut [u8], w: usize, h: usize, channels: usize) {
let row_bytes = w * channels;
for y in 0..h {
let row_start = y * row_bytes;
let mut left = 0;
let mut right = (w - 1) * channels;
while left < right {
for c in 0..channels {
buf.swap(row_start + left + c, row_start + right + c);
}
left += channels;
right -= channels;
}
}
}