device_envoy_core/cyd/display/
tga.rs1use crate::cyd::display::CydFrame;
4use embedded_graphics::{
5 Drawable, Pixel,
6 pixelcolor::{Rgb565, raw::RawU16},
7 prelude::{DrawTarget, Point, Size},
8 primitives::Rectangle,
9};
10
11pub const fn mask_byte_count(width: usize, height: usize) -> usize {
13 (width * height).div_ceil(8)
14}
15
16pub struct Image888Fixed<const W: usize, const H: usize, const N: usize> {
18 pub pixels: [[u8; 3]; N],
20}
21
22pub struct Image565Fixed<const W: usize, const H: usize, const N: usize> {
24 pub pixels: [u16; N],
26}
27
28pub struct MaskFixed<const W: usize, const H: usize, const MASK_N: usize> {
30 pub bits: [u8; MASK_N],
32}
33
34pub struct PlacedImage565<'a, const W: usize, const H: usize, const N: usize> {
35 image: &'a Image565Fixed<W, H, N>,
36 top_left: Point,
37}
38
39const fn read_u16(bytes: &[u8], offset: usize) -> u16 {
40 bytes[offset] as u16 | ((bytes[offset + 1] as u16) << 8)
41}
42
43const fn parse_header(bytes: &[u8], width: usize, height: usize) -> (usize, usize, bool) {
44 assert!(bytes.len() >= 18, "TGA: file shorter than header");
45 assert!(bytes[1] == 0, "TGA: color maps are not supported");
46 assert!(
47 bytes[2] == 2,
48 "TGA: only uncompressed true-color images are supported"
49 );
50 assert!(
51 bytes[3] == 0 && bytes[4] == 0 && bytes[5] == 0 && bytes[6] == 0 && bytes[7] == 0,
52 "TGA: color map specification is not supported"
53 );
54 assert!(
55 read_u16(bytes, 12) as usize == width,
56 "TGA: width does not match const argument"
57 );
58 assert!(
59 read_u16(bytes, 14) as usize == height,
60 "TGA: height does not match const argument"
61 );
62 assert!(
63 bytes[16] == 24 || bytes[16] == 32,
64 "TGA: only 24-bit BGR or 32-bit BGRA is supported"
65 );
66 assert!(
67 bytes[17] & 0x10 == 0,
68 "TGA: right-to-left origin is not supported"
69 );
70 let bytes_per_pixel = (bytes[16] / 8) as usize;
71 let pixel_start = 18 + bytes[0] as usize;
72 assert!(
73 bytes.len() >= pixel_start + width * height * bytes_per_pixel,
74 "TGA: pixel data is shorter than width * height"
75 );
76 (pixel_start, bytes_per_pixel, bytes[17] & 0x20 != 0)
77}
78
79impl<const W: usize, const H: usize, const N: usize> Image888Fixed<W, H, N> {
80 pub const fn from_tga(bytes: &[u8]) -> Self {
82 assert!(N == W * H, "Image888Fixed: N must equal W * H");
83 let (pixel_start, bytes_per_pixel, top_origin) = parse_header(bytes, W, H);
84 let mut pixels = [[0u8; 3]; N];
85 let mut y = 0;
86 while y < H {
87 let mut x = 0;
88 while x < W {
89 let source_y = if top_origin { y } else { H - 1 - y };
90 let offset = pixel_start + (source_y * W + x) * bytes_per_pixel;
91 let red = bytes[offset + 2];
92 let green = bytes[offset + 1];
93 let blue = bytes[offset];
94 pixels[y * W + x] = [red, green, blue];
95 x += 1;
96 }
97 y += 1;
98 }
99 Self { pixels }
100 }
101
102 pub const fn to_565(&self) -> Image565Fixed<W, H, N> {
104 let mut pixels = [0u16; N];
105 let mut index = 0;
106 while index < N {
107 let [red, green, blue] = self.pixels[index];
108 pixels[index] =
109 ((red as u16 >> 3) << 11) | ((green as u16 >> 2) << 5) | (blue as u16 >> 3);
110 index += 1;
111 }
112 Image565Fixed { pixels }
113 }
114
115 pub const fn to_mask_magenta<const MASK_N: usize>(&self) -> MaskFixed<W, H, MASK_N> {
117 assert!(
118 MASK_N == mask_byte_count(W, H),
119 "Mask: MASK_N must match image dimensions"
120 );
121 let mut bits = [0u8; MASK_N];
122 let mut index = 0;
123 while index < N {
124 let pixel = self.pixels[index];
125 let red = pixel[0];
126 let green = pixel[1];
127 let blue = pixel[2];
128 if !(red >= 200 && blue >= 200 && green <= 60) {
129 bits[index / 8] |= 1 << (index % 8);
130 }
131 index += 1;
132 }
133 MaskFixed { bits }
134 }
135}
136
137impl<const W: usize, const H: usize, const N: usize> Image565Fixed<W, H, N> {
138 pub const fn at(&self, top_left: Point) -> PlacedImage565<'_, W, H, N> {
139 PlacedImage565 {
140 image: self,
141 top_left,
142 }
143 }
144
145 pub const fn view(&'static self) -> super::Image565View {
146 self.view_rect(Rectangle::new(Point::zero(), Size::new(W as u32, H as u32)))
147 }
148
149 pub const fn view_rect(&'static self, source: Rectangle) -> super::Image565View {
150 assert!(
151 source.top_left.x >= 0 && source.top_left.y >= 0,
152 "view_rect: negative origin"
153 );
154 assert!(
155 source.top_left.x as usize + source.size.width as usize <= W
156 && source.top_left.y as usize + source.size.height as usize <= H,
157 "view_rect: rectangle is outside image"
158 );
159 super::Image565View::new_cropped(&self.pixels, W as u32, source)
160 }
161
162 pub fn copy_to<F: CydFrame>(&self, frame: &mut F) -> crate::Result<()> {
163 frame.copy_from_565(&self.pixels)
164 }
165}
166
167impl<const W: usize, const H: usize, const MASK_N: usize> MaskFixed<W, H, MASK_N> {
168 pub const fn is_set(&self, index: usize) -> bool {
169 self.bits[index / 8] & (1 << (index % 8)) != 0
170 }
171}
172
173impl<'a, const W: usize, const H: usize, const N: usize> PlacedImage565<'a, W, H, N> {
174 pub fn draw_masked<const MASK_N: usize, D>(
175 &self,
176 mask: &MaskFixed<W, H, MASK_N>,
177 target: &mut D,
178 ) -> Result<(), D::Error>
179 where
180 D: DrawTarget<Color = Rgb565>,
181 {
182 let mut pixels = ImagePixels::<W, N> {
183 pixels: &self.image.pixels,
184 top_left: self.top_left,
185 index: 0,
186 };
187 target.draw_iter(core::iter::from_fn(|| {
188 loop {
189 let pixel = pixels.next()?;
190 let index = pixels.index - 1;
191 if mask.is_set(index) {
192 return Some(pixel);
193 }
194 }
195 }))
196 }
197}
198
199struct ImagePixels<'a, const W: usize, const N: usize> {
200 pixels: &'a [u16; N],
201 top_left: Point,
202 index: usize,
203}
204
205impl<const W: usize, const N: usize> Iterator for ImagePixels<'_, W, N> {
206 type Item = Pixel<Rgb565>;
207 fn next(&mut self) -> Option<Self::Item> {
208 if self.index >= N {
209 return None;
210 }
211 let index = self.index;
212 self.index += 1;
213 Some(Pixel(
214 self.top_left + Point::new((index % W) as i32, (index / W) as i32),
215 Rgb565::from(RawU16::new(self.pixels[index])),
216 ))
217 }
218}
219
220impl<const W: usize, const H: usize, const N: usize> Drawable for PlacedImage565<'_, W, H, N> {
221 type Color = Rgb565;
222 type Output = ();
223 fn draw<D>(&self, target: &mut D) -> Result<(), D::Error>
224 where
225 D: DrawTarget<Color = Rgb565>,
226 {
227 target.draw_iter(ImagePixels::<W, N> {
228 pixels: &self.image.pixels,
229 top_left: self.top_left,
230 index: 0,
231 })
232 }
233}
234
235impl<const W: usize, const H: usize, const N: usize> Drawable for Image565Fixed<W, H, N> {
236 type Color = Rgb565;
237 type Output = ();
238 fn draw<D>(&self, target: &mut D) -> Result<(), D::Error>
239 where
240 D: DrawTarget<Color = Rgb565>,
241 {
242 self.at(Point::zero()).draw(target)
243 }
244}
245
246#[doc(hidden)]
247#[macro_export]
248macro_rules! __cyd_tga {
249 ($path:expr) => {
250 $crate::cyd::display::Image888Fixed::from_tga(include_bytes!($path))
251 };
252 ($path:expr, $width:expr, $height:expr) => {
253 $crate::cyd::display::Image888Fixed::<$width, $height, { $width * $height }>::from_tga(
254 include_bytes!($path),
255 )
256 };
257}