simple-tga-reader 0.1.0

Simple TGA image reader
Documentation
//! Port of imagefmt's TGA decoder written in D, licensed under BSD 2-Clause.
//!
//! See [LICENSE](https://github.com/tjhann/imagefmt/blob/master/LICENSE)
//!
//! https://github.com/tjhann/imagefmt

use std::error::Error;
use std::fmt;
use std::io::{self, Read, Seek, Write};

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum DataType {
	NoData = 0,
	Idx = 1,
	TrueColor = 2,
	Gray = 3,
	IdxRle = 9,
	TruecolorRle = 10,
	GrayRle = 11,
}

#[derive(Debug)]
pub struct InvalidDataType(u8);

impl Error for InvalidDataType {}

impl fmt::Display for InvalidDataType {
	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
		write!(f, "Invalid data type ({})", self.0)
	}
}

impl TryFrom<u8> for DataType {
	type Error = InvalidDataType;

	fn try_from(value: u8) -> Result<Self, Self::Error> {
		Ok(match value {
			0 => Self::NoData,
			1 => Self::Idx,
			2 => Self::TrueColor,
			3 => Self::Gray,
			9 => Self::IdxRle,
			10 => Self::TruecolorRle,
			11 => Self::GrayRle,
			n => return Err(InvalidDataType(n)),
		})
	}
}

#[derive(Clone, Debug)]
pub struct TgaHeader {
	pub width: u16,
	pub height: u16,
	pub id_len: u8,
	pub palette_type: u8,
	pub data_type: DataType,
	pub bits_pp: u8,
	pub flags: u8,
}

#[derive(Debug)]
pub enum TgaDecodeError {
	Io(io::Error),
	InvalidHeader,
	Unsupported(&'static str),
	TooBig,
}

impl Error for TgaDecodeError {
	fn source(&self) -> Option<&(dyn Error + 'static)> {
		match self {
			TgaDecodeError::Io(err) => Some(err),
			_ => None,
		}
	}
}

impl fmt::Display for TgaDecodeError {
	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
		match self {
			Self::Io(_) => write!(f, "IO error when decoding TGA file"),
			Self::InvalidHeader => write!(f, "Invalid TGA file header"),
			Self::Unsupported(reason) => write!(f, "Unsupported TGA file: {}", reason),
			Self::TooBig => write!(f, "Image is too big"),
		}
	}
}

impl From<io::Error> for TgaDecodeError {
	fn from(err: io::Error) -> Self {
		Self::Io(err)
	}
}

// TGA doesn't have a signature so validate some values right here for detection.
fn read_tga_header<R: Read + Seek>(reader: &mut R) -> Result<TgaHeader, TgaDecodeError> {
	let id_len = read_u8(reader)?;
	let palette_type = read_u8(reader)?;
	let data_type = read_u8(reader)?.try_into().map_err(|_| TgaDecodeError::InvalidHeader)?;
	let palette_beg = read_le_u16(reader)?;
	let palette_len = read_le_u16(reader)?;
	let palette_bits = read_u8(reader)?;

	reader.seek(io::SeekFrom::Current(2 + 2))?; // origin (x, y)

	let width = read_le_u16(reader)?;
	let height = read_le_u16(reader)?;
	let bits_pp = read_u8(reader)?;
	let flags = read_u8(reader)?;

	if width < 1
		|| height < 1
		|| palette_type > 1
		|| (palette_type == 0 && (palette_beg > 0 || palette_len > 0 || palette_bits > 0))
	{
		return Err(TgaDecodeError::InvalidHeader);
	}

	Ok(TgaHeader {
		id_len,
		palette_type,
		data_type,
		width,
		height,
		bits_pp,
		flags,
	})
}

#[derive(Clone, Debug)]
pub struct TgaImage {
	pub header: TgaHeader,
	pub data: Vec<u8>,
	pub channels: TgaChannels,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum TgaChannels {
	Y = 1,
	Ya = 2,
	Bgr = 3,
	Bgra = 4,
}

#[derive(Debug)]
pub struct InvalidTgaChannelCount(u8);

impl Error for InvalidTgaChannelCount {}

impl fmt::Display for InvalidTgaChannelCount {
	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
		write!(f, "Invalid TGA channel count ({})", self.0)
	}
}

impl TryFrom<u8> for TgaChannels {
	type Error = InvalidTgaChannelCount;

	fn try_from(value: u8) -> Result<Self, Self::Error> {
		Ok(match value {
			1 => Self::Y,
			2 => Self::Ya,
			3 => Self::Bgr,
			4 => Self::Bgra,
			n => return Err(InvalidTgaChannelCount(n)),
		})
	}
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum BitsPerChannel {
	B8,
	B16,
}

const TGA_FLAG_INTERLACED: u8 = 0xc0;
const TGA_FLAG_RIGHT_TO_LEFT: u8 = 0x10;
const TGA_FLAG_BITSPP: u8 = 0x0f;
const TGA_FLAG_ORIGIN_AT_TOP: u8 = 0x20;

const TGA_FLAG_PACKET_IS_RLE: u8 = 0x80;
const TGA_FLAG_PACKET_LEN: u8 = 0x7f;

const TGA_MAXIMUM_IMAGE_SIZE: u64 = 0x7fff_ffff;

/// Reads a TGA image into RGBA
pub fn read_tga<R: Read + Seek>(reader: &mut R) -> Result<TgaImage, TgaDecodeError> {
	let header = read_tga_header(reader)?;

	if header.flags & TGA_FLAG_INTERLACED > 0 {
		return Err(TgaDecodeError::Unsupported("interlaced"));
	}
	if header.flags & TGA_FLAG_RIGHT_TO_LEFT > 0 {
		return Err(TgaDecodeError::Unsupported("right-to-left"));
	}

	let attr_bits_pp = header.flags & TGA_FLAG_BITSPP;
	if attr_bits_pp != 0 && attr_bits_pp != 8 {
		// some set to 0 even if data has 8
		return Err(TgaDecodeError::Unsupported("bits per pixel != 8"));
	}
	if header.palette_type > 0 {
		return Err(TgaDecodeError::Unsupported("palette type != 0"));
	}

	match header.data_type {
		DataType::TrueColor | DataType::TruecolorRle => {
			if header.bits_pp != 24 && header.bits_pp != 32 {
				return Err(TgaDecodeError::Unsupported("bits per pixel != 24 or 32"));
			}
		}
		DataType::Gray | DataType::GrayRle => {
			if header.bits_pp != 8 && !(header.bits_pp == 16 && attr_bits_pp == 8) {
				return Err(TgaDecodeError::Unsupported("unsupported bits per pixel"));
			}
		}
		DataType::NoData => {
			return Err(TgaDecodeError::Unsupported("no data type"));
		}
		DataType::Idx => {
			return Err(TgaDecodeError::Unsupported("idx data type"));
		}
		DataType::IdxRle => {
			return Err(TgaDecodeError::Unsupported("idx rle data type"));
		}
	}

	let is_origin_at_top = header.flags & TGA_FLAG_ORIGIN_AT_TOP > 0;
	let is_rle = matches!(
		header.data_type,
		DataType::IdxRle | DataType::GrayRle | DataType::TruecolorRle
	);

	let channels: TgaChannels = (header.bits_pp / 8).try_into().unwrap(); // bytes per pixel
	let tchans = 4;
	let linebuf_size = header.width * channels as u16;
	let tline_size = header.width * tchans;

	let flip = !is_origin_at_top;
	let tstride = if flip { -(tline_size as i32) } else { tline_size as i32 };
	let mut ti = if flip {
		(header.height as isize - 1) * tline_size as isize
	} else {
		0
	} as usize;

	if header.width as u64 * header.height as u64 * tchans as u64 > TGA_MAXIMUM_IMAGE_SIZE {
		return Err(TgaDecodeError::TooBig);
	}

	let mut data = vec![0_u8; header.width as usize * header.height as usize * tchans as usize];
	let mut linebuf = vec![0_u8; linebuf_size as usize];

	if header.id_len > 0 {
		reader.seek(io::SeekFrom::Current(header.id_len as i64))?;
	}

	if !is_rle {
		for _ in 0..header.height {
			reader.read_exact(&mut linebuf)?;
			to_rgba(channels, &linebuf, &mut data[ti..ti + tline_size as usize])?;
			ti = ti.saturating_add_signed(tstride as isize);
		}
	} else {
		let mut pixel = [0_u8; 4];
		let mut packet_len = 0;
		let mut is_rle = false;

		for _ in 0..header.height {
			let mut wanted = linebuf_size as usize; // fill linebuf with unpacked data
			while wanted > 0 {
				if packet_len == 0 {
					let packet_head = read_u8(reader)?;
					is_rle = packet_head & TGA_FLAG_PACKET_IS_RLE > 0;
					packet_len = ((packet_head & TGA_FLAG_PACKET_LEN) + 1) as usize * channels as usize;
				}

				let gotten = linebuf_size as usize - wanted;
				let copy_size = wanted.min(packet_len);
				if is_rle {
					let channels = channels as usize;
					reader.read_exact(&mut pixel[..channels])?;

					let mut p = gotten;
					while p < gotten + copy_size {
						let mut place = &mut linebuf[p..p + channels];
						place.write_all(&pixel[..channels])?;
						p += channels;
					}
				} else {
					// raw packet
					reader.read_exact(&mut linebuf[gotten..gotten + copy_size])?;
				}

				wanted -= copy_size;
				packet_len -= copy_size;
			}

			to_rgba(channels, &linebuf, &mut data[ti..ti + tline_size as usize])?;
			ti = ti.saturating_add_signed(tstride as isize);
		}
	}

	Ok(TgaImage { header, data, channels })
}

fn to_rgba(channels: TgaChannels, src: &[u8], tgt: &mut [u8]) -> io::Result<()> {
	match channels {
		TgaChannels::Y => y_to_rgba(src, tgt),
		TgaChannels::Ya => ya_to_rgba(src, tgt),
		TgaChannels::Bgr => bgr_to_rgba(src, tgt),
		TgaChannels::Bgra => bgra_to_rgba(src, tgt),
	}
}

fn y_to_rgba(src: &[u8], tgt: &mut [u8]) -> io::Result<()> {
	for i in 0..src.len() {
		let (k, t) = (i, i * 4);
		let mut tgt = &mut tgt[t..t + 3];
		tgt.write_all(&[src[k]; 3])?;
		tgt[t + 3] = 255;
	}

	Ok(())
}

fn ya_to_rgba(src: &[u8], tgt: &mut [u8]) -> io::Result<()> {
	for i in 0..src.len() / 2 {
		let (k, t) = (i * 2, i * 4);
		let mut tgt = &mut tgt[t..t + 3];
		tgt.write_all(&[src[k]; 3])?;
		tgt[t + 3] = src[k + 1];
	}

	Ok(())
}

fn bgr_to_rgba(src: &[u8], tgt: &mut [u8]) -> io::Result<()> {
	for i in 0..src.len() / 3 {
		let (k, t) = (i * 3, i * 4);
		tgt[t] = src[k + 2];
		tgt[t + 1] = src[k + 1];
		tgt[t + 2] = src[k];
		tgt[t + 3] = 255;
	}

	Ok(())
}

fn bgra_to_rgba(src: &[u8], tgt: &mut [u8]) -> io::Result<()> {
	for i in 0..src.len() / 4 {
		let (k, t) = (i * 4, i * 4);
		tgt[t] = src[k + 2];
		tgt[t + 1] = src[k + 1];
		tgt[t + 2] = src[k];
		tgt[t + 3] = src[k + 3];
	}

	Ok(())
}

#[inline]
fn read_u8<R: Read>(data: &mut R) -> io::Result<u8> {
	let mut buf: [u8; 1] = [0];
	data.read_exact(&mut buf)?;
	Ok(u8::from_ne_bytes(buf))
}

#[inline]
fn read_le_u16<R: Read>(data: &mut R) -> io::Result<u16> {
	let mut buf: [u8; 2] = [0, 0];
	data.read_exact(&mut buf)?;
	Ok(u16::from_le_bytes(buf))
}