rlvgl_widgets/motion/
background.rs1use rlvgl_platform::blit::{PixelFmt, Surface};
18
19pub trait BackgroundPattern {
27 fn paint(&self, surface: &mut Surface<'_>);
33}
34
35#[derive(Copy, Clone, Debug)]
45pub struct StarField {
46 pub star_count: u16,
48 pub prng_seed: u32,
50 pub bg_color: u32,
52 pub brightness: (u8, u8),
54}
55
56impl Default for StarField {
57 fn default() -> Self {
58 Self {
59 star_count: 200,
60 prng_seed: 0xDEAD_BEEF,
61 bg_color: 0xFF0A_0A20,
63 brightness: (128, 255),
64 }
65 }
66}
67
68impl StarField {
69 #[inline]
71 pub const fn new(star_count: u16, prng_seed: u32, bg_color: u32, brightness: (u8, u8)) -> Self {
72 Self {
73 star_count,
74 prng_seed,
75 bg_color,
76 brightness,
77 }
78 }
79}
80
81impl BackgroundPattern for StarField {
82 fn paint(&self, surface: &mut Surface<'_>) {
83 if surface.format != PixelFmt::Argb8888 {
84 return;
85 }
86 let bpp = 4usize;
87 let stride = surface.stride;
88 let w = surface.width as usize;
89 let h = surface.height as usize;
90 if w == 0 || h == 0 {
91 return;
92 }
93
94 let bg = self.bg_color.to_le_bytes();
97 for y in 0..h {
98 let row_start = y * stride;
99 for x in 0..w {
100 let off = row_start + x * bpp;
101 if off + bpp > surface.buf.len() {
102 return;
103 }
104 surface.buf[off..off + bpp].copy_from_slice(&bg);
105 }
106 }
107
108 let mut rng = self.prng_seed.max(1); let (b_lo, b_hi) = self.brightness;
112 let b_range = b_hi.saturating_sub(b_lo) as u32 + 1;
113 for _ in 0..self.star_count {
114 rng ^= rng << 13;
115 rng ^= rng >> 17;
116 rng ^= rng << 5;
117 let xy = rng as usize % (w * h);
118 let x = xy % w;
119 let y = xy / w;
120 rng ^= rng << 13;
121 rng ^= rng >> 17;
122 rng ^= rng << 5;
123 let brightness = b_lo.saturating_add((rng % b_range) as u8);
124 let off = y * stride + x * bpp;
125 if off + bpp > surface.buf.len() {
126 continue;
127 }
128 surface.buf[off] = brightness;
130 surface.buf[off + 1] = brightness;
131 surface.buf[off + 2] = brightness;
132 surface.buf[off + 3] = 0xFF;
133 }
134 }
135}
136
137#[cfg(test)]
138mod tests {
139 use super::*;
140 use alloc::vec;
141
142 fn make_surface(w: u32, h: u32, buf: &mut [u8]) -> Surface<'_> {
143 Surface::new(buf, w as usize * 4, PixelFmt::Argb8888, w, h)
144 }
145
146 #[test]
147 fn paints_background_colour() {
148 let mut buf = vec![0u8; 8 * 4 * 4];
149 let sf = StarField {
150 star_count: 0,
151 ..Default::default()
152 };
153 sf.paint(&mut make_surface(8, 4, &mut buf));
154 for px in buf.chunks_exact(4) {
156 assert_eq!(px, &0xFF0A_0A20u32.to_le_bytes());
157 }
158 }
159
160 #[test]
161 fn scatters_stars_deterministically() {
162 let mut buf_a = vec![0u8; 64 * 64 * 4];
163 let mut buf_b = vec![0u8; 64 * 64 * 4];
164 let sf = StarField::default();
165 sf.paint(&mut make_surface(64, 64, &mut buf_a));
166 sf.paint(&mut make_surface(64, 64, &mut buf_b));
167 assert_eq!(buf_a, buf_b, "same seed must produce identical output");
168 }
169
170 #[test]
171 fn different_seed_yields_different_output() {
172 let mut buf_a = vec![0u8; 64 * 64 * 4];
173 let mut buf_b = vec![0u8; 64 * 64 * 4];
174 let sf_a = StarField::default();
175 let sf_b = StarField {
176 prng_seed: 0xCAFE_F00D,
177 ..Default::default()
178 };
179 sf_a.paint(&mut make_surface(64, 64, &mut buf_a));
180 sf_b.paint(&mut make_surface(64, 64, &mut buf_b));
181 assert_ne!(buf_a, buf_b, "different seed should scatter differently");
182 }
183
184 #[test]
185 fn at_least_one_star_has_non_bg_pixel() {
186 let mut buf = vec![0u8; 64 * 64 * 4];
187 let sf = StarField::default();
188 sf.paint(&mut make_surface(64, 64, &mut buf));
189 let bg = 0xFF0A_0A20u32.to_le_bytes();
191 let has_star = buf.chunks_exact(4).any(|px| px != bg);
192 assert!(has_star, "starfield produced no visible stars");
193 }
194
195 #[test]
196 fn non_argb_format_is_no_op() {
197 let mut buf = vec![0xAAu8; 16 * 16 * 2];
198 {
199 let mut surface = Surface::new(&mut buf, 16 * 2, PixelFmt::Rgb565, 16, 16);
200 StarField::default().paint(&mut surface);
201 }
202 assert!(buf.iter().all(|&b| b == 0xAA));
204 }
205
206 #[test]
207 fn zero_seed_does_not_panic() {
208 let mut buf = vec![0u8; 16 * 16 * 4];
209 let sf = StarField {
210 prng_seed: 0,
211 ..Default::default()
212 };
213 sf.paint(&mut make_surface(16, 16, &mut buf));
214 }
215}