tinytga/lib.rs
1//! A small TGA parser designed for use with [embedded-graphics] targeting no-std environments but
2//! usable anywhere. Beyond parsing the image header, no other allocations are made.
3//!
4//! tinytga provides two methods of accessing the pixel data inside a TGA file. The most convenient
5//! way is to use a color type provided by [embedded-graphics] to define the format stored inside
6//! the TGA file. But it is also possible to directly access the raw pixel representation instead.
7//!
8//! # Examples
9//!
10//! ## Using `Tga` to draw an image
11//!
12//! This example demonstrates how a TGA image can be drawn to a [embedded-graphics] draw target.
13//!
14//! ```rust
15//! # fn main() -> Result<(), core::convert::Infallible> {
16//! # let mut display = embedded_graphics::mock_display::MockDisplay::default();
17//! use embedded_graphics::{image::Image, pixelcolor::Rgb888, prelude::*};
18//! use tinytga::Tga;
19//!
20//! // Include an image from a local path as bytes
21//! let data = include_bytes!("../tests/chessboard_4px_rle.tga");
22//!
23//! let tga: Tga<Rgb888> = Tga::from_slice(data).unwrap();
24//!
25//! let image = Image::new(&tga, Point::zero());
26//!
27//! image.draw(&mut display)?;
28//! # Ok::<(), core::convert::Infallible>(()) }
29//! ```
30//!
31//! ## Accessing pixels using an embedded-graphics color type
32//!
33//! If [embedded-graphics] is not used to draw the TGA image, the color types provided by
34//! [embedded-graphics] can still be used to access the pixel data using the
35//! [`pixels`](struct.Tga.html#method.pixels) method.
36//!
37//! ```rust
38//! use embedded_graphics::{prelude::*, pixelcolor::Rgb888};
39//! use tinytga::Tga;
40//!
41//! // Include an image from a local path as bytes
42//! let data = include_bytes!("../tests/chessboard_4px_rle.tga");
43//!
44//! // Create a TGA instance from a byte slice.
45//! // The color type is set by defining the type of the `img` variable.
46//! let img: Tga<Rgb888> = Tga::from_slice(data).unwrap();
47//!
48//! // Check the size of the image.
49//! assert_eq!(img.size(), Size::new(4, 4));
50//!
51//! // Collect pixels into a vector.
52//! let pixels: Vec<_> = img.pixels().collect();
53//! ```
54//!
55//! ## Accessing raw pixel data
56//!
57//! If [embedded-graphics] is not used in the target application, the raw image data can be
58//! accessed with the [`pixels`](struct.RawTga.html#method.pixels) method on
59//! [`RawTga`]. The returned iterator produces a `u32` for each pixel value.
60//!
61//! ```rust
62//! use embedded_graphics::{prelude::*, pixelcolor::Rgb888};
63//! use tinytga::{Bpp, Compression, DataType, ImageOrigin, RawPixel, RawTga, TgaHeader};
64//!
65//! // Include an image from a local path as bytes.
66//! let data = include_bytes!("../tests/chessboard_4px_rle.tga");
67//!
68//! // Create a TGA instance from a byte slice.
69//! let img = RawTga::from_slice(data).unwrap();
70//!
71//! // Take a look at the raw image header.
72//! assert_eq!(
73//! img.header(),
74//! TgaHeader {
75//! id_len: 0,
76//! has_color_map: false,
77//! data_type: DataType::TrueColor,
78//! compression: Compression::Rle,
79//! color_map_start: 0,
80//! color_map_len: 0,
81//! color_map_depth: None,
82//! x_origin: 0,
83//! y_origin: 4,
84//! width: 4,
85//! height: 4,
86//! pixel_depth: Bpp::Bits24,
87//! image_origin: ImageOrigin::TopLeft,
88//! alpha_channel_depth: 0,
89//! }
90//! );
91//!
92//! // Collect raw pixels into a vector.
93//! let pixels: Vec<_> = img.pixels().collect();
94//! ```
95//!
96//! # Embedded-graphics drawing performance
97//!
98//! `tinytga` uses different code paths to draw images with different [`ImageOrigin`]s.
99//! The performance difference between the origins will depend on the display driver, but using
100//! images with the origin at the top left corner will generally result in the best performance.
101//!
102//! # Minimum supported Rust version
103//!
104//! The minimum supported Rust version for tinytga is `1.61` or greater.
105//! Ensure you have the correct version of Rust installed, preferably through <https://rustup.rs>.
106//!
107//! [`ImageOrigin`]: enum.ImageOrigin.html
108//! [embedded-graphics]: https://docs.rs/embedded-graphics
109//! [`Tga`]: ./struct.Tga.html
110//! [`RawTga`]: ./struct.RawTga.html
111
112#![no_std]
113#![deny(missing_docs)]
114#![deny(missing_debug_implementations)]
115#![deny(missing_copy_implementations)]
116#![deny(trivial_casts)]
117#![deny(trivial_numeric_casts)]
118#![deny(unsafe_code)]
119#![deny(unstable_features)]
120#![deny(unused_import_braces)]
121#![deny(unused_qualifications)]
122
123mod color_map;
124mod footer;
125mod header;
126mod parse_error;
127mod pixels;
128mod raw_iter;
129mod raw_tga;
130
131use core::marker::PhantomData;
132use embedded_graphics::{
133 pixelcolor::{
134 raw::{RawU16, RawU24, RawU8},
135 Gray8, Rgb555, Rgb888,
136 },
137 prelude::*,
138 primitives::Rectangle,
139};
140use raw_iter::{RawColors, Rle, Uncompressed};
141
142pub use crate::{
143 color_map::ColorMap,
144 header::{Bpp, Compression, DataType, ImageOrigin, TgaHeader},
145 parse_error::ParseError,
146 pixels::Pixels,
147 raw_iter::{RawPixel, RawPixels},
148 raw_tga::RawTga,
149};
150
151/// TGA image.
152#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
153pub struct Tga<'a, C> {
154 /// Raw TGA file.
155 raw: RawTga<'a>,
156
157 image_color_type: ColorType,
158
159 /// Color type.
160 target_color_type: PhantomData<C>,
161}
162
163impl<'a, C> Tga<'a, C>
164where
165 C: PixelColor + From<Gray8> + From<Rgb555> + From<Rgb888>,
166{
167 /// Parses a TGA image from a byte slice.
168 pub fn from_slice(data: &'a [u8]) -> Result<Self, ParseError> {
169 let raw = RawTga::from_slice(data)?;
170
171 let image_color_type = match (raw.color_bpp(), raw.data_type()) {
172 (Bpp::Bits8, DataType::BlackAndWhite) => ColorType::Gray8,
173 (Bpp::Bits16, DataType::ColorMapped) => ColorType::Rgb555,
174 (Bpp::Bits16, DataType::TrueColor) => ColorType::Rgb555,
175 (Bpp::Bits24, DataType::ColorMapped) => ColorType::Rgb888,
176 (Bpp::Bits24, DataType::TrueColor) => ColorType::Rgb888,
177 _ => {
178 return Err(ParseError::UnsupportedTgaType(
179 raw.data_type(),
180 raw.color_bpp(),
181 ));
182 }
183 };
184
185 Ok(Tga {
186 raw,
187 image_color_type,
188 target_color_type: PhantomData,
189 })
190 }
191
192 /// Returns a reference to the raw TGA image.
193 ///
194 /// The [`RawTga`] object can be used to access lower level details about the TGA file.
195 ///
196 /// [`RawTga`]: struct.RawTga.html
197 pub fn as_raw(&self) -> &RawTga<'a> {
198 &self.raw
199 }
200
201 /// Returns an iterator over the pixels in this image.
202 pub fn pixels(&self) -> Pixels<'_, C> {
203 Pixels::new(self)
204 }
205
206 fn draw_colors<D>(
207 &self,
208 target: &mut D,
209 mut colors: impl Iterator<Item = C>,
210 ) -> Result<(), D::Error>
211 where
212 D: DrawTarget<Color = C>,
213 {
214 let bounding_box = self.bounding_box();
215 if bounding_box.is_zero_sized() {
216 return Ok(());
217 }
218
219 let origin = self.raw.image_origin();
220
221 // TGA files with the origin in the top left corner can be drawn using `fill_contiguous`.
222 // All other origins are drawn by falling back to `draw_iter`.
223 match origin {
224 ImageOrigin::TopLeft => target.fill_contiguous(&bounding_box, colors),
225 ImageOrigin::BottomLeft => {
226 let mut row_rect =
227 Rectangle::new(Point::zero(), Size::new(bounding_box.size.width, 1));
228
229 for y in bounding_box.rows().rev() {
230 row_rect.top_left.y = y;
231 let row_colors = (&mut colors).take(bounding_box.size.width as usize);
232 target.fill_contiguous(&row_rect, row_colors)?;
233 }
234
235 Ok(())
236 }
237 ImageOrigin::TopRight => {
238 let max_x = bounding_box.bottom_right().map(|p| p.x).unwrap_or_default();
239
240 bounding_box
241 .points()
242 .zip(colors)
243 .map(|(p, c)| Pixel(Point::new(max_x - p.x, p.y), c))
244 .draw(target)
245 }
246 ImageOrigin::BottomRight => {
247 let bottom_right = bounding_box.bottom_right().unwrap_or_default();
248
249 bounding_box
250 .points()
251 .zip(colors)
252 .map(|(p, c)| Pixel(bottom_right - p, c))
253 .draw(target)
254 }
255 }
256 }
257
258 fn draw_regular<D, CI, F>(
259 &self,
260 target: &mut D,
261 colors: RawColors<'a, CI::Raw, F>,
262 ) -> Result<(), D::Error>
263 where
264 D: DrawTarget<Color = C>,
265 CI: PixelColor + From<CI::Raw> + Into<C>,
266 RawColors<'a, CI::Raw, F>: Iterator<Item = CI::Raw>,
267 {
268 self.draw_colors(target, colors.map(|c| CI::from(c).into()))
269 }
270
271 fn draw_color_mapped<D, R, F>(
272 &self,
273 target: &mut D,
274 indices: RawColors<'a, R, F>,
275 ) -> Result<(), D::Error>
276 where
277 D: DrawTarget<Color = C>,
278 R: RawData,
279 R::Storage: Into<u32>,
280 RawColors<'a, R, F>: Iterator<Item = R>,
281 {
282 let color_map = if let Some(color_map) = self.raw.color_map() {
283 color_map
284 } else {
285 return Ok(());
286 };
287
288 match self.image_color_type {
289 ColorType::Rgb555 => {
290 let colors = indices.map(|index| {
291 let index = index.into_inner().into() as usize;
292 color_map.get::<Rgb555>(index).unwrap().into()
293 });
294
295 self.draw_colors(target, colors)
296 }
297 ColorType::Rgb888 => {
298 let colors = indices.map(|index| {
299 let index = index.into_inner().into() as usize;
300 color_map.get::<Rgb888>(index).unwrap().into()
301 });
302
303 self.draw_colors(target, colors)
304 }
305 // Color mapped Gray8 images aren't supported. Using a color map for Gray8 images
306 // doesn't make sense, because this encoding will always be larger than a type 3 image.
307 ColorType::Gray8 => Ok(()),
308 }
309 }
310}
311
312impl<C> OriginDimensions for Tga<'_, C> {
313 fn size(&self) -> Size {
314 self.raw.size()
315 }
316}
317
318impl<C> ImageDrawable for Tga<'_, C>
319where
320 C: PixelColor + From<Gray8> + From<Rgb555> + From<Rgb888>,
321{
322 type Color = C;
323
324 fn draw<D>(&self, target: &mut D) -> Result<(), D::Error>
325 where
326 D: DrawTarget<Color = C>,
327 {
328 match self.raw.image_data_bpp() {
329 Bpp::Bits8 => match self.raw.compression() {
330 Compression::Uncompressed => {
331 let colors = RawColors::<RawU8, Uncompressed>::new(&self.raw);
332
333 if self.raw.color_map().is_some() {
334 self.draw_color_mapped(target, colors)
335 } else {
336 self.draw_regular::<_, Gray8, _>(target, colors)
337 }
338 }
339 Compression::Rle => {
340 let colors = RawColors::<RawU8, Rle>::new(&self.raw);
341
342 if self.raw.color_map().is_some() {
343 self.draw_color_mapped(target, colors)
344 } else {
345 self.draw_regular::<_, Gray8, _>(target, colors)
346 }
347 }
348 },
349 Bpp::Bits16 => match self.raw.compression() {
350 Compression::Uncompressed => {
351 let colors = RawColors::<RawU16, Uncompressed>::new(&self.raw);
352
353 if self.raw.color_map().is_some() {
354 self.draw_color_mapped(target, colors)
355 } else {
356 self.draw_regular::<_, Rgb555, _>(target, colors)
357 }
358 }
359 Compression::Rle => {
360 let colors = RawColors::<RawU16, Rle>::new(&self.raw);
361
362 if self.raw.color_map().is_some() {
363 self.draw_color_mapped(target, colors)
364 } else {
365 self.draw_regular::<_, Rgb555, _>(target, colors)
366 }
367 }
368 },
369 Bpp::Bits24 => match self.raw.compression() {
370 Compression::Uncompressed => {
371 let colors = RawColors::<RawU24, Uncompressed>::new(&self.raw);
372
373 if self.raw.color_map().is_some() {
374 self.draw_color_mapped(target, colors)
375 } else {
376 self.draw_regular::<_, Rgb888, _>(target, colors)
377 }
378 }
379 Compression::Rle => {
380 let colors = RawColors::<RawU24, Rle>::new(&self.raw);
381
382 if self.raw.color_map().is_some() {
383 self.draw_color_mapped(target, colors)
384 } else {
385 self.draw_regular::<_, Rgb888, _>(target, colors)
386 }
387 }
388 },
389 Bpp::Bits32 => Ok(()),
390 }
391 }
392
393 fn draw_sub_image<D>(&self, target: &mut D, area: &Rectangle) -> Result<(), D::Error>
394 where
395 D: DrawTarget<Color = Self::Color>,
396 {
397 self.draw(&mut target.translated(-area.top_left).clipped(area))
398 }
399}
400
401#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
402pub(crate) enum ColorType {
403 Gray8,
404 Rgb555,
405 Rgb888,
406}