1mod reader;
35mod writer;
36
37pub use reader::{
38 read_tga, read_tga_from_bytes, read_tga_from_bytes_with_options, read_tga_with_options,
39};
40pub use writer::{write_tga, write_tga_to_vec};
41
42use thiserror::Error;
43use tinytga::{Bpp, ParseError};
44
45use crate::binary::{DecodeBinary, EncodeBinary};
46
47#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
49pub enum TgaDataType {
50 NoData,
52 ColorMapped,
54 TrueColor,
56 BlackAndWhite,
58}
59
60#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
62pub enum TgaCompression {
63 Uncompressed,
65 Rle,
67}
68
69#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
71pub enum TgaOrigin {
72 BottomLeft,
74 BottomRight,
76 TopLeft,
78 TopRight,
80}
81
82#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
84pub enum TgaBitsPerPixel {
85 Bits8,
87 Bits16,
89 Bits24,
91 Bits32,
93}
94
95impl TgaBitsPerPixel {
96 pub(super) fn try_from_tinytga(value: Bpp) -> Result<Self, TgaBinaryError> {
97 match value {
98 Bpp::Bits8 => Ok(Self::Bits8),
99 Bpp::Bits16 => Ok(Self::Bits16),
100 Bpp::Bits24 => Ok(Self::Bits24),
101 Bpp::Bits32 => Ok(Self::Bits32),
102 _ => Err(TgaBinaryError::InvalidHeader(
103 "unsupported bits-per-pixel value in source header".into(),
104 )),
105 }
106 }
107
108 pub fn bits(self) -> u8 {
110 match self {
111 Self::Bits8 => 8,
112 Self::Bits16 => 16,
113 Self::Bits24 => 24,
114 Self::Bits32 => 32,
115 }
116 }
117}
118
119#[derive(Debug, Clone, PartialEq, Eq, Hash)]
121pub struct TgaHeader {
122 pub id_len: u8,
124 pub has_color_map: bool,
126 pub data_type: TgaDataType,
128 pub compression: TgaCompression,
130 pub color_map_start: u16,
132 pub color_map_len: u16,
134 pub color_map_depth: Option<TgaBitsPerPixel>,
136 pub x_origin: u16,
138 pub y_origin: u16,
140 pub width: u16,
142 pub height: u16,
144 pub pixel_depth: TgaBitsPerPixel,
146 pub image_origin: TgaOrigin,
148 pub alpha_channel_depth: u8,
150}
151
152#[derive(Debug, Clone, PartialEq, Eq)]
156pub struct Tga {
157 pub header: TgaHeader,
159 pub image_id: Vec<u8>,
161 pub rgba_pixels: Vec<u8>,
163}
164
165impl Tga {
166 pub fn new_rgba(width: u16, height: u16, rgba_pixels: Vec<u8>) -> Result<Self, TgaBinaryError> {
168 validate_rgba_len(width, height, &rgba_pixels)?;
169
170 Ok(Self {
171 header: TgaHeader {
172 id_len: 0,
173 has_color_map: false,
174 data_type: TgaDataType::TrueColor,
175 compression: TgaCompression::Uncompressed,
176 color_map_start: 0,
177 color_map_len: 0,
178 color_map_depth: None,
179 x_origin: 0,
180 y_origin: 0,
181 width,
182 height,
183 pixel_depth: TgaBitsPerPixel::Bits32,
184 image_origin: TgaOrigin::TopLeft,
185 alpha_channel_depth: 8,
186 },
187 image_id: Vec::new(),
188 rgba_pixels,
189 })
190 }
191}
192
193impl DecodeBinary for Tga {
194 type Error = TgaBinaryError;
195
196 fn decode_binary(bytes: &[u8]) -> Result<Self, Self::Error> {
197 read_tga_from_bytes(bytes)
198 }
199}
200
201impl EncodeBinary for Tga {
202 type Error = TgaBinaryError;
203
204 fn encode_binary(&self) -> Result<Vec<u8>, Self::Error> {
205 write_tga_to_vec(self)
206 }
207}
208
209#[derive(Debug, Error)]
211pub enum TgaBinaryError {
212 #[error(transparent)]
214 Io(#[from] std::io::Error),
215 #[error("TGA parse error: {0:?}")]
217 Parse(ParseError),
218 #[error("invalid TGA header: {0}")]
220 InvalidHeader(String),
221 #[error("invalid TGA data: {0}")]
223 InvalidData(String),
224 #[error("value overflow while handling field `{0}`")]
226 ValueOverflow(&'static str),
227}
228
229impl From<ParseError> for TgaBinaryError {
230 fn from(value: ParseError) -> Self {
231 Self::Parse(value)
232 }
233}
234
235#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
237pub struct TgaReadOptions {
238 pub input: TgaReadMode,
240}
241
242impl Default for TgaReadOptions {
243 fn default() -> Self {
244 Self {
245 input: TgaReadMode::CanonicalK1,
246 }
247 }
248}
249
250#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
252pub enum TgaReadMode {
253 CanonicalK1,
258 Compatibility,
260}
261
262pub(super) fn checked_pixel_count(width: usize, height: usize) -> Result<usize, TgaBinaryError> {
263 width
264 .checked_mul(height)
265 .ok_or(TgaBinaryError::ValueOverflow("pixel count"))
266}
267
268pub(super) fn validate_rgba_len(
269 width: u16,
270 height: u16,
271 rgba: &[u8],
272) -> Result<(), TgaBinaryError> {
273 let pixel_count = checked_pixel_count(usize::from(width), usize::from(height))?;
274 let expected_len = pixel_count
275 .checked_mul(4)
276 .ok_or(TgaBinaryError::ValueOverflow("rgba length"))?;
277
278 if expected_len != rgba.len() {
279 return Err(TgaBinaryError::InvalidData(format!(
280 "RGBA length mismatch: expected {expected_len}, got {}",
281 rgba.len()
282 )));
283 }
284
285 Ok(())
286}