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}