simple_tga_reader/
lib.rs

1//! Port of imagefmt's TGA decoder written in D, licensed under BSD 2-Clause.
2//!
3//! See [LICENSE](https://github.com/tjhann/imagefmt/blob/master/LICENSE)
4//!
5//! https://github.com/tjhann/imagefmt
6
7use std::error::Error;
8use std::fmt;
9use std::io::{self, Read, Seek, Write};
10
11#[derive(Clone, Copy, Debug, PartialEq, Eq)]
12#[repr(u8)]
13pub enum DataType {
14	NoData = 0,
15	Idx = 1,
16	TrueColor = 2,
17	Gray = 3,
18	IdxRle = 9,
19	TruecolorRle = 10,
20	GrayRle = 11,
21}
22
23#[derive(Debug)]
24pub struct InvalidDataType(u8);
25
26impl Error for InvalidDataType {}
27
28impl fmt::Display for InvalidDataType {
29	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
30		write!(f, "Invalid data type ({})", self.0)
31	}
32}
33
34impl TryFrom<u8> for DataType {
35	type Error = InvalidDataType;
36
37	fn try_from(value: u8) -> Result<Self, Self::Error> {
38		Ok(match value {
39			0 => Self::NoData,
40			1 => Self::Idx,
41			2 => Self::TrueColor,
42			3 => Self::Gray,
43			9 => Self::IdxRle,
44			10 => Self::TruecolorRle,
45			11 => Self::GrayRle,
46			n => return Err(InvalidDataType(n)),
47		})
48	}
49}
50
51#[derive(Clone, Debug)]
52pub struct TgaHeader {
53	pub width: u16,
54	pub height: u16,
55	pub id_len: u8,
56	pub palette_type: u8,
57	pub data_type: DataType,
58	pub bits_pp: u8,
59	pub flags: u8,
60}
61
62#[derive(Debug)]
63pub enum TgaDecodeError {
64	Io(io::Error),
65	InvalidHeader,
66	Unsupported(&'static str),
67	TooBig,
68}
69
70impl Error for TgaDecodeError {
71	fn source(&self) -> Option<&(dyn Error + 'static)> {
72		match self {
73			TgaDecodeError::Io(err) => Some(err),
74			_ => None,
75		}
76	}
77}
78
79impl fmt::Display for TgaDecodeError {
80	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81		match self {
82			Self::Io(_) => write!(f, "IO error when decoding TGA file"),
83			Self::InvalidHeader => write!(f, "Invalid TGA file header"),
84			Self::Unsupported(reason) => write!(f, "Unsupported TGA file: {}", reason),
85			Self::TooBig => write!(f, "Image is too big"),
86		}
87	}
88}
89
90impl From<io::Error> for TgaDecodeError {
91	fn from(err: io::Error) -> Self {
92		Self::Io(err)
93	}
94}
95
96// TGA doesn't have a signature so validate some values right here for detection.
97fn read_tga_header<R: Read + Seek>(reader: &mut R) -> Result<TgaHeader, TgaDecodeError> {
98	let id_len = read_u8(reader)?;
99	let palette_type = read_u8(reader)?;
100	let data_type = read_u8(reader)?.try_into().map_err(|_| TgaDecodeError::InvalidHeader)?;
101	let palette_beg = read_le_u16(reader)?;
102	let palette_len = read_le_u16(reader)?;
103	let palette_bits = read_u8(reader)?;
104
105	reader.seek(io::SeekFrom::Current(2 + 2))?; // origin (x, y)
106
107	let width = read_le_u16(reader)?;
108	let height = read_le_u16(reader)?;
109	let bits_pp = read_u8(reader)?;
110	let flags = read_u8(reader)?;
111
112	if width < 1
113		|| height < 1
114		|| palette_type > 1
115		|| (palette_type == 0 && (palette_beg > 0 || palette_len > 0 || palette_bits > 0))
116	{
117		return Err(TgaDecodeError::InvalidHeader);
118	}
119
120	Ok(TgaHeader {
121		id_len,
122		palette_type,
123		data_type,
124		width,
125		height,
126		bits_pp,
127		flags,
128	})
129}
130
131#[derive(Clone, Debug)]
132pub struct TgaImage {
133	pub header: TgaHeader,
134	pub data: Vec<u8>,
135	pub channels: TgaChannels,
136}
137
138#[derive(Clone, Copy, Debug, PartialEq, Eq)]
139#[repr(u8)]
140pub enum TgaChannels {
141	Y = 1,
142	Ya = 2,
143	Bgr = 3,
144	Bgra = 4,
145}
146
147#[derive(Debug)]
148pub struct InvalidTgaChannelCount(u8);
149
150impl Error for InvalidTgaChannelCount {}
151
152impl fmt::Display for InvalidTgaChannelCount {
153	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
154		write!(f, "Invalid TGA channel count ({})", self.0)
155	}
156}
157
158impl TryFrom<u8> for TgaChannels {
159	type Error = InvalidTgaChannelCount;
160
161	fn try_from(value: u8) -> Result<Self, Self::Error> {
162		Ok(match value {
163			1 => Self::Y,
164			2 => Self::Ya,
165			3 => Self::Bgr,
166			4 => Self::Bgra,
167			n => return Err(InvalidTgaChannelCount(n)),
168		})
169	}
170}
171
172#[derive(Clone, Copy, Debug, PartialEq, Eq)]
173pub enum BitsPerChannel {
174	B8,
175	B16,
176}
177
178const TGA_FLAG_INTERLACED: u8 = 0xc0;
179const TGA_FLAG_RIGHT_TO_LEFT: u8 = 0x10;
180const TGA_FLAG_BITSPP: u8 = 0x0f;
181const TGA_FLAG_ORIGIN_AT_TOP: u8 = 0x20;
182
183const TGA_FLAG_PACKET_IS_RLE: u8 = 0x80;
184const TGA_FLAG_PACKET_LEN: u8 = 0x7f;
185
186const TGA_MAXIMUM_IMAGE_SIZE: u64 = 0x7fff_ffff;
187
188/// Reads a TGA image into RGBA
189pub fn read_tga<R: Read + Seek>(reader: &mut R) -> Result<TgaImage, TgaDecodeError> {
190	let header = read_tga_header(reader)?;
191
192	if header.flags & TGA_FLAG_INTERLACED > 0 {
193		return Err(TgaDecodeError::Unsupported("interlaced"));
194	}
195	if header.flags & TGA_FLAG_RIGHT_TO_LEFT > 0 {
196		return Err(TgaDecodeError::Unsupported("right-to-left"));
197	}
198
199	let attr_bits_pp = header.flags & TGA_FLAG_BITSPP;
200	if attr_bits_pp != 0 && attr_bits_pp != 8 {
201		// some set to 0 even if data has 8
202		return Err(TgaDecodeError::Unsupported("bits per pixel != 8"));
203	}
204	if header.palette_type > 0 {
205		return Err(TgaDecodeError::Unsupported("palette type != 0"));
206	}
207
208	match header.data_type {
209		DataType::TrueColor | DataType::TruecolorRle => {
210			if header.bits_pp != 24 && header.bits_pp != 32 {
211				return Err(TgaDecodeError::Unsupported("bits per pixel != 24 or 32"));
212			}
213		}
214		DataType::Gray | DataType::GrayRle => {
215			if header.bits_pp != 8 && !(header.bits_pp == 16 && attr_bits_pp == 8) {
216				return Err(TgaDecodeError::Unsupported("unsupported bits per pixel"));
217			}
218		}
219		DataType::NoData => {
220			return Err(TgaDecodeError::Unsupported("no data type"));
221		}
222		DataType::Idx => {
223			return Err(TgaDecodeError::Unsupported("idx data type"));
224		}
225		DataType::IdxRle => {
226			return Err(TgaDecodeError::Unsupported("idx rle data type"));
227		}
228	}
229
230	let is_origin_at_top = header.flags & TGA_FLAG_ORIGIN_AT_TOP > 0;
231	let is_rle = matches!(
232		header.data_type,
233		DataType::IdxRle | DataType::GrayRle | DataType::TruecolorRle
234	);
235
236	let channels: TgaChannels = (header.bits_pp / 8).try_into().unwrap(); // bytes per pixel
237	let tchans = 4;
238	let linebuf_size = header.width * channels as u16;
239	let tline_size = header.width * tchans;
240
241	let flip = !is_origin_at_top;
242	let tstride = if flip { -(tline_size as i32) } else { tline_size as i32 };
243	let mut ti = if flip {
244		(header.height as isize - 1) * tline_size as isize
245	} else {
246		0
247	} as usize;
248
249	if header.width as u64 * header.height as u64 * tchans as u64 > TGA_MAXIMUM_IMAGE_SIZE {
250		return Err(TgaDecodeError::TooBig);
251	}
252
253	let mut data = vec![0_u8; header.width as usize * header.height as usize * tchans as usize];
254	let mut linebuf = vec![0_u8; linebuf_size as usize];
255
256	if header.id_len > 0 {
257		reader.seek(io::SeekFrom::Current(header.id_len as i64))?;
258	}
259
260	if !is_rle {
261		for _ in 0..header.height {
262			reader.read_exact(&mut linebuf)?;
263			to_rgba(channels, &linebuf, &mut data[ti..ti + tline_size as usize])?;
264			ti = ti.saturating_add_signed(tstride as isize);
265		}
266	} else {
267		let mut pixel = [0_u8; 4];
268		let mut packet_len = 0;
269		let mut is_rle = false;
270
271		for _ in 0..header.height {
272			let mut wanted = linebuf_size as usize; // fill linebuf with unpacked data
273			while wanted > 0 {
274				if packet_len == 0 {
275					let packet_head = read_u8(reader)?;
276					is_rle = packet_head & TGA_FLAG_PACKET_IS_RLE > 0;
277					packet_len = ((packet_head & TGA_FLAG_PACKET_LEN) + 1) as usize * channels as usize;
278				}
279
280				let gotten = linebuf_size as usize - wanted;
281				let copy_size = wanted.min(packet_len);
282				if is_rle {
283					let channels = channels as usize;
284					reader.read_exact(&mut pixel[..channels])?;
285
286					let mut p = gotten;
287					while p < gotten + copy_size {
288						let mut place = &mut linebuf[p..p + channels];
289						place.write_all(&pixel[..channels])?;
290						p += channels;
291					}
292				} else {
293					// raw packet
294					reader.read_exact(&mut linebuf[gotten..gotten + copy_size])?;
295				}
296
297				wanted -= copy_size;
298				packet_len -= copy_size;
299			}
300
301			to_rgba(channels, &linebuf, &mut data[ti..ti + tline_size as usize])?;
302			ti = ti.saturating_add_signed(tstride as isize);
303		}
304	}
305
306	Ok(TgaImage { header, data, channels })
307}
308
309fn to_rgba(channels: TgaChannels, src: &[u8], tgt: &mut [u8]) -> io::Result<()> {
310	match channels {
311		TgaChannels::Y => y_to_rgba(src, tgt),
312		TgaChannels::Ya => ya_to_rgba(src, tgt),
313		TgaChannels::Bgr => bgr_to_rgba(src, tgt),
314		TgaChannels::Bgra => bgra_to_rgba(src, tgt),
315	}
316}
317
318fn y_to_rgba(src: &[u8], tgt: &mut [u8]) -> io::Result<()> {
319	for i in 0..src.len() {
320		let (k, t) = (i, i * 4);
321		let mut tgt = &mut tgt[t..t + 3];
322		tgt.write_all(&[src[k]; 3])?;
323		tgt[t + 3] = 255;
324	}
325
326	Ok(())
327}
328
329fn ya_to_rgba(src: &[u8], tgt: &mut [u8]) -> io::Result<()> {
330	for i in 0..src.len() / 2 {
331		let (k, t) = (i * 2, i * 4);
332		let mut tgt = &mut tgt[t..t + 3];
333		tgt.write_all(&[src[k]; 3])?;
334		tgt[t + 3] = src[k + 1];
335	}
336
337	Ok(())
338}
339
340fn bgr_to_rgba(src: &[u8], tgt: &mut [u8]) -> io::Result<()> {
341	for i in 0..src.len() / 3 {
342		let (k, t) = (i * 3, i * 4);
343		tgt[t] = src[k + 2];
344		tgt[t + 1] = src[k + 1];
345		tgt[t + 2] = src[k];
346		tgt[t + 3] = 255;
347	}
348
349	Ok(())
350}
351
352fn bgra_to_rgba(src: &[u8], tgt: &mut [u8]) -> io::Result<()> {
353	for i in 0..src.len() / 4 {
354		let (k, t) = (i * 4, i * 4);
355		tgt[t] = src[k + 2];
356		tgt[t + 1] = src[k + 1];
357		tgt[t + 2] = src[k];
358		tgt[t + 3] = src[k + 3];
359	}
360
361	Ok(())
362}
363
364#[inline]
365fn read_u8<R: Read>(data: &mut R) -> io::Result<u8> {
366	let mut buf: [u8; 1] = [0];
367	data.read_exact(&mut buf)?;
368	Ok(u8::from_ne_bytes(buf))
369}
370
371#[inline]
372fn read_le_u16<R: Read>(data: &mut R) -> io::Result<u16> {
373	let mut buf: [u8; 2] = [0, 0];
374	data.read_exact(&mut buf)?;
375	Ok(u16::from_le_bytes(buf))
376}