use rlvgl_platform::blit::{PixelFmt, Surface};
pub trait BackgroundPattern {
fn paint(&self, surface: &mut Surface<'_>);
}
#[derive(Copy, Clone, Debug)]
pub struct StarField {
pub star_count: u16,
pub prng_seed: u32,
pub bg_color: u32,
pub brightness: (u8, u8),
}
impl Default for StarField {
fn default() -> Self {
Self {
star_count: 200,
prng_seed: 0xDEAD_BEEF,
bg_color: 0xFF0A_0A20,
brightness: (128, 255),
}
}
}
impl StarField {
#[inline]
pub const fn new(star_count: u16, prng_seed: u32, bg_color: u32, brightness: (u8, u8)) -> Self {
Self {
star_count,
prng_seed,
bg_color,
brightness,
}
}
}
impl BackgroundPattern for StarField {
fn paint(&self, surface: &mut Surface<'_>) {
if surface.format != PixelFmt::Argb8888 {
return;
}
let bpp = 4usize;
let stride = surface.stride;
let w = surface.width as usize;
let h = surface.height as usize;
if w == 0 || h == 0 {
return;
}
let bg = self.bg_color.to_le_bytes();
for y in 0..h {
let row_start = y * stride;
for x in 0..w {
let off = row_start + x * bpp;
if off + bpp > surface.buf.len() {
return;
}
surface.buf[off..off + bpp].copy_from_slice(&bg);
}
}
let mut rng = self.prng_seed.max(1); let (b_lo, b_hi) = self.brightness;
let b_range = b_hi.saturating_sub(b_lo) as u32 + 1;
for _ in 0..self.star_count {
rng ^= rng << 13;
rng ^= rng >> 17;
rng ^= rng << 5;
let xy = rng as usize % (w * h);
let x = xy % w;
let y = xy / w;
rng ^= rng << 13;
rng ^= rng >> 17;
rng ^= rng << 5;
let brightness = b_lo.saturating_add((rng % b_range) as u8);
let off = y * stride + x * bpp;
if off + bpp > surface.buf.len() {
continue;
}
surface.buf[off] = brightness;
surface.buf[off + 1] = brightness;
surface.buf[off + 2] = brightness;
surface.buf[off + 3] = 0xFF;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec;
fn make_surface(w: u32, h: u32, buf: &mut [u8]) -> Surface<'_> {
Surface::new(buf, w as usize * 4, PixelFmt::Argb8888, w, h)
}
#[test]
fn paints_background_colour() {
let mut buf = vec![0u8; 8 * 4 * 4];
let sf = StarField {
star_count: 0,
..Default::default()
};
sf.paint(&mut make_surface(8, 4, &mut buf));
for px in buf.chunks_exact(4) {
assert_eq!(px, &0xFF0A_0A20u32.to_le_bytes());
}
}
#[test]
fn scatters_stars_deterministically() {
let mut buf_a = vec![0u8; 64 * 64 * 4];
let mut buf_b = vec![0u8; 64 * 64 * 4];
let sf = StarField::default();
sf.paint(&mut make_surface(64, 64, &mut buf_a));
sf.paint(&mut make_surface(64, 64, &mut buf_b));
assert_eq!(buf_a, buf_b, "same seed must produce identical output");
}
#[test]
fn different_seed_yields_different_output() {
let mut buf_a = vec![0u8; 64 * 64 * 4];
let mut buf_b = vec![0u8; 64 * 64 * 4];
let sf_a = StarField::default();
let sf_b = StarField {
prng_seed: 0xCAFE_F00D,
..Default::default()
};
sf_a.paint(&mut make_surface(64, 64, &mut buf_a));
sf_b.paint(&mut make_surface(64, 64, &mut buf_b));
assert_ne!(buf_a, buf_b, "different seed should scatter differently");
}
#[test]
fn at_least_one_star_has_non_bg_pixel() {
let mut buf = vec![0u8; 64 * 64 * 4];
let sf = StarField::default();
sf.paint(&mut make_surface(64, 64, &mut buf));
let bg = 0xFF0A_0A20u32.to_le_bytes();
let has_star = buf.chunks_exact(4).any(|px| px != bg);
assert!(has_star, "starfield produced no visible stars");
}
#[test]
fn non_argb_format_is_no_op() {
let mut buf = vec![0xAAu8; 16 * 16 * 2];
{
let mut surface = Surface::new(&mut buf, 16 * 2, PixelFmt::Rgb565, 16, 16);
StarField::default().paint(&mut surface);
}
assert!(buf.iter().all(|&b| b == 0xAA));
}
#[test]
fn zero_seed_does_not_panic() {
let mut buf = vec![0u8; 16 * 16 * 4];
let sf = StarField {
prng_seed: 0,
..Default::default()
};
sf.paint(&mut make_surface(16, 16, &mut buf));
}
}