embedded_sprites/
image.rs

1use core::{
2	fmt,
3	fmt::{Debug, Display, Formatter},
4};
5use embedded_graphics::pixelcolor::PixelColor;
6
7/// Store image data.
8///
9/// Image can be constructed using the [`include_image`](crate::include_image) macro at compile time.
10/// To draw an image it must be put inside a [`Sprite`](crate::sprite::Sprite).
11#[derive(Debug)]
12pub struct Image<'a, C: PixelColor> {
13	pub(crate) width: u16,
14	pub(crate) transparenty: &'a [u8],
15	pub(crate) colors: &'a [C],
16}
17
18#[derive(Debug, Eq, PartialEq)]
19pub enum Dimension {
20	Width,
21	Height,
22}
23
24#[derive(Debug, Eq, PartialEq)]
25pub enum Error {
26	WrongPixelLength(Dimension),
27}
28
29impl Display for Error {
30	fn fmt(&self, f: &mut Formatter) -> fmt::Result {
31		match self {
32			Self::WrongPixelLength(dimension) => match dimension {
33				Dimension::Width => write!(f, "length of `colors` is is not divisible by `width`"),
34				Dimension::Height => write!(f, "`colors.len()/width` does not match provided `height`"),
35			},
36		}
37	}
38}
39impl Error {
40	#[doc(hidden)]
41	// const unwrap; see https://docs.rs/konst/0.3.4/konst/result/macro.unwrap_ctx.html
42	pub const fn panic(&self) -> ! {
43		match self {
44			Self::WrongPixelLength(dimension) => match dimension {
45				Dimension::Width => panic!("Error creating image:	length of `colors` is is not divisible by `width`"),
46				Dimension::Height => panic!("Error creating image:	`colors.len()/width` does not match provided `height`"),
47			},
48		}
49	}
50}
51
52impl<'a, C: PixelColor> Image<'a, C> {
53	/// create a new [Image] from a array of [PixelColor]s, a transparenty map.
54	///
55	/// Return an error, if the length of `colors` does not fit to the widht and height.
56	/// You can unwrap the error in a const contexts,
57	/// by using the [konst](https://crates.io/crates/konst) crate see [`konst::result::unwrap_ctx`](https://docs.rs/konst/0.3.4/konst/result/macro.unwrap_ctx.html) for more informations.
58	///
59	/// `transparency` can be created by using the [`transparency!`](crate::transparency) macro.
60	/// It's length doesn’t have to match the image length, missing data will be interpreted as fully opaque.
61	///
62	/// ```
63	/// use embedded_graphics::pixelcolor::Bgr565;
64	/// use embedded_sprites::{image::Image, transparency};
65	/// use konst::result::unwrap_ctx;
66	///
67	/// type Color = Bgr565;
68	/// const IMAGE_DATA: [Color; 6] = [
69	/// 	Color::new(255, 0, 0),
70	/// 	Color::new(0, 255, 0),
71	/// 	Color::new(0, 0, 255),
72	/// 	Color::new(255, 0, 255),
73	/// 	Color::new(255, 255, 255),
74	/// 	Color::new(255, 255, 255),
75	/// ];
76	/// const IMAGE: Image<Color> =
77	/// 	unwrap_ctx!(Image::new(&IMAGE_DATA, &transparency![0, 0, 0, 1, 0], 3, 2));
78	/// ```
79
80	pub const fn new(colors: &'a [C], transparenty: &'a [u8], width: u16, height: u16) -> Result<Self, Error> {
81		if colors.len() % width as usize != 0 {
82			return Err(Error::WrongPixelLength(Dimension::Width));
83		};
84		if colors.len() / width as usize != height as usize {
85			return Err(Error::WrongPixelLength(Dimension::Height));
86		};
87		Ok(Image {
88			colors,
89			transparenty,
90			width,
91		})
92	}
93}
94
95/// Utility macro to construct a transparency array from bits. Can be used when creating an [`Image`].
96///
97/// The number of bits doesn't have to match the image length, missing data will be interpreted as fully opaque.
98///
99/// ```
100/// # use embedded_sprites::transparency;
101/// let transparency = transparency![0, 0, 1, 0];
102/// ```
103///
104/// The result is that the 3rd pixel is transparent, and all other pixels are opaque.
105#[macro_export]
106macro_rules! transparency {
107	($($x:expr),*) => {
108		{
109			const N: usize = [$($x),*].len();
110			const LEN: usize = N / 8 + if N % 8 > 0 { 1 } else { 0 };
111			const T: [u8; LEN] = {
112				let mut t = [0u8; LEN];
113				let mut i = 0;
114				let mut j = 7;
115				$(
116					t[i] |= ($x & 1 ) << j;
117					#[allow(unused_assignments)]
118					if j == 0 {
119						j = 7;
120						i += 1;
121					} else {
122						j -= 1;
123					}
124				)*
125					t
126			};
127			T
128		}
129	};
130}
131
132#[cfg(test)]
133mod tests {
134	use super::{Dimension, Error, Image};
135	use crate::transparency;
136	use embedded_graphics::pixelcolor::Bgr565;
137	use konst::result::unwrap_ctx;
138
139	type Color = Bgr565;
140
141	const IMAGE_DATA: [Color; 6] = [
142		Color::new(255, 0, 0),
143		Color::new(0, 255, 0),
144		Color::new(0, 0, 255),
145		Color::new(255, 0, 255),
146		Color::new(255, 255, 255),
147		Color::new(255, 255, 255),
148	];
149
150	#[test]
151	fn create_const_image() {
152		#[allow(dead_code)]
153		const IMAGE1: Image<Color> = unwrap_ctx!(Image::new(&IMAGE_DATA, &transparency![0, 0, 0, 1, 0, 0], 3, 2));
154		#[allow(dead_code)]
155		const IMAGE2: Image<Color> = unwrap_ctx!(Image::new(&IMAGE_DATA, &transparency![0, 0, 0, 0], 3, 2));
156		//todo: check if iterator of image is identical if I put them inside a sprite
157	}
158	#[test]
159	fn create_image_wrong_widht() {
160		assert_eq!(
161			Image::new(&IMAGE_DATA, &transparency![0, 0, 0, 0], 4, 2).unwrap_err(),
162			Error::WrongPixelLength(Dimension::Width)
163		);
164	}
165	#[test]
166	fn create_image_wrong_hight() {
167		assert_eq!(
168			Image::new(&IMAGE_DATA, &transparency![0, 0, 0, 0], 3, 3).unwrap_err(),
169			Error::WrongPixelLength(Dimension::Height)
170		);
171	}
172}