use embedded_graphics_core::pixelcolor::Rgb565;
use heapless::Vec as HeaplessVec;
#[derive(Debug, Clone, Copy)]
pub struct Texture {
pub data: &'static [Rgb565],
pub width: u32,
pub height: u32,
width_mask: u32,
height_mask: u32,
}
impl Texture {
pub fn new(data: &'static [Rgb565], width: u32, height: u32) -> Self {
assert!(width.is_power_of_two(), "Texture width must be power of 2");
assert!(
height.is_power_of_two(),
"Texture height must be power of 2"
);
assert_eq!(
data.len(),
(width * height) as usize,
"Texture data length must match width × height"
);
Self {
data,
width,
height,
width_mask: width - 1,
height_mask: height - 1,
}
}
#[inline]
pub fn sample(&self, u: f32, v: f32) -> Rgb565 {
let tex_x = (u * self.width as f32) as u32;
let tex_y = (v * self.height as f32) as u32;
let tex_x = tex_x & self.width_mask;
let tex_y = tex_y & self.height_mask;
self.data[(tex_y * self.width + tex_x) as usize]
}
#[inline]
pub fn sample_fixed(&self, u_fixed: u32, v_fixed: u32) -> Rgb565 {
let tex_x = ((u_fixed >> 16) * self.width) >> 16;
let tex_y = ((v_fixed >> 16) * self.height) >> 16;
let tex_x = tex_x & self.width_mask;
let tex_y = tex_y & self.height_mask;
self.data[(tex_y * self.width + tex_x) as usize]
}
pub fn dimensions(&self) -> (u32, u32) {
(self.width, self.height)
}
}
pub struct TextureManager<const N: usize> {
textures: HeaplessVec<Texture, N>,
}
impl<const N: usize> TextureManager<N> {
pub fn new() -> Self {
Self {
textures: HeaplessVec::new(),
}
}
pub fn add_texture(&mut self, texture: Texture) -> Option<u32> {
self.textures.push(texture).ok()?;
Some((self.textures.len() - 1) as u32)
}
pub fn get(&self, id: u32) -> Option<&Texture> {
self.textures.get(id as usize)
}
pub fn len(&self) -> usize {
self.textures.len()
}
pub fn is_empty(&self) -> bool {
self.textures.is_empty()
}
pub fn is_full(&self) -> bool {
self.textures.len() >= N
}
}
impl<const N: usize> Default for TextureManager<N> {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
extern crate std;
use super::*;
use embedded_graphics_core::pixelcolor::{Rgb565, WebColors};
#[test]
fn test_texture_creation() {
static DATA: [Rgb565; 64] = [Rgb565::CSS_RED; 64];
let texture = Texture::new(&DATA, 8, 8);
assert_eq!(texture.width, 8);
assert_eq!(texture.height, 8);
assert_eq!(texture.dimensions(), (8, 8));
}
#[test]
#[should_panic(expected = "width must be power of 2")]
fn test_texture_non_power_of_2_width() {
static DATA: [Rgb565; 60] = [Rgb565::CSS_RED; 60];
let _texture = Texture::new(&DATA, 10, 6); }
#[test]
#[should_panic(expected = "height must be power of 2")]
fn test_texture_non_power_of_2_height() {
static DATA: [Rgb565; 48] = [Rgb565::CSS_RED; 48];
let _texture = Texture::new(&DATA, 8, 6); }
#[test]
#[should_panic(expected = "length must match")]
fn test_texture_wrong_data_length() {
static DATA: [Rgb565; 60] = [Rgb565::CSS_RED; 60];
let _texture = Texture::new(&DATA, 8, 8); }
#[test]
fn test_texture_sampling() {
static DATA: [Rgb565; 16] = [
Rgb565::CSS_RED,
Rgb565::CSS_GREEN,
Rgb565::CSS_BLUE,
Rgb565::CSS_YELLOW,
Rgb565::CSS_CYAN,
Rgb565::CSS_MAGENTA,
Rgb565::CSS_WHITE,
Rgb565::CSS_BLACK,
Rgb565::CSS_RED,
Rgb565::CSS_GREEN,
Rgb565::CSS_BLUE,
Rgb565::CSS_YELLOW,
Rgb565::CSS_CYAN,
Rgb565::CSS_MAGENTA,
Rgb565::CSS_WHITE,
Rgb565::CSS_BLACK,
];
let texture = Texture::new(&DATA, 4, 4);
let tl = texture.sample(0.0, 0.0);
assert_eq!(tl, Rgb565::CSS_RED);
let mid = texture.sample(0.5, 0.5);
assert_eq!(mid, Rgb565::CSS_BLUE);
}
#[test]
fn test_texture_wrapping() {
static DATA: [Rgb565; 16] = [Rgb565::CSS_RED; 16];
let texture = Texture::new(&DATA, 4, 4);
let wrapped = texture.sample(1.5, 1.5);
assert_eq!(wrapped, Rgb565::CSS_RED);
}
#[test]
fn test_texture_manager() {
static DATA1: [Rgb565; 16] = [Rgb565::CSS_RED; 16];
static DATA2: [Rgb565; 64] = [Rgb565::CSS_GREEN; 64];
let mut manager = TextureManager::<4>::new();
assert!(manager.is_empty());
assert!(!manager.is_full());
let id1 = manager.add_texture(Texture::new(&DATA1, 4, 4));
assert_eq!(id1, Some(0));
assert_eq!(manager.len(), 1);
let id2 = manager.add_texture(Texture::new(&DATA2, 8, 8));
assert_eq!(id2, Some(1));
assert_eq!(manager.len(), 2);
let tex1 = manager.get(0).unwrap();
assert_eq!(tex1.width, 4);
let tex2 = manager.get(1).unwrap();
assert_eq!(tex2.width, 8);
}
#[test]
fn test_texture_manager_full() {
static DATA: [Rgb565; 16] = [Rgb565::CSS_RED; 16];
let mut manager = TextureManager::<2>::new();
assert!(manager.add_texture(Texture::new(&DATA, 4, 4)).is_some());
assert!(manager.add_texture(Texture::new(&DATA, 4, 4)).is_some());
assert!(manager.is_full());
assert!(manager.add_texture(Texture::new(&DATA, 4, 4)).is_none());
}
}