schematic_mesher/resource_pack/
texture.rs

1//! Texture loading and handling.
2
3/// Raw texture data loaded from PNG.
4#[derive(Debug, Clone)]
5pub struct TextureData {
6    /// Texture width in pixels.
7    pub width: u32,
8    /// Texture height in pixels.
9    pub height: u32,
10    /// RGBA8 pixel data (4 bytes per pixel).
11    pub pixels: Vec<u8>,
12    /// Whether this texture has animation metadata.
13    pub is_animated: bool,
14    /// Animation frame count (1 if not animated).
15    pub frame_count: u32,
16}
17
18impl TextureData {
19    /// Create a new texture from RGBA data.
20    pub fn new(width: u32, height: u32, pixels: Vec<u8>) -> Self {
21        Self {
22            width,
23            height,
24            pixels,
25            is_animated: false,
26            frame_count: 1,
27        }
28    }
29
30    /// Create a placeholder texture (magenta/black checkerboard).
31    pub fn placeholder() -> Self {
32        let size = 16;
33        let mut pixels = vec![0u8; (size * size * 4) as usize];
34
35        for y in 0..size {
36            for x in 0..size {
37                let idx = ((y * size + x) * 4) as usize;
38                let is_magenta = ((x / 2) + (y / 2)) % 2 == 0;
39
40                if is_magenta {
41                    pixels[idx] = 255; // R
42                    pixels[idx + 1] = 0; // G
43                    pixels[idx + 2] = 255; // B
44                    pixels[idx + 3] = 255; // A
45                } else {
46                    pixels[idx] = 0; // R
47                    pixels[idx + 1] = 0; // G
48                    pixels[idx + 2] = 0; // B
49                    pixels[idx + 3] = 255; // A
50                }
51            }
52        }
53
54        Self {
55            width: size,
56            height: size,
57            pixels,
58            is_animated: false,
59            frame_count: 1,
60        }
61    }
62
63    /// Check if this texture has transparency.
64    pub fn has_transparency(&self) -> bool {
65        self.pixels.chunks(4).any(|pixel| pixel[3] < 255)
66    }
67
68    /// Get a pixel at (x, y).
69    pub fn get_pixel(&self, x: u32, y: u32) -> [u8; 4] {
70        let idx = ((y * self.width + x) * 4) as usize;
71        [
72            self.pixels[idx],
73            self.pixels[idx + 1],
74            self.pixels[idx + 2],
75            self.pixels[idx + 3],
76        ]
77    }
78
79    /// Get the first frame of an animated texture (or the whole texture if not animated).
80    pub fn first_frame(&self) -> TextureData {
81        if !self.is_animated || self.frame_count <= 1 {
82            return self.clone();
83        }
84
85        let frame_height = self.height / self.frame_count;
86        let frame_size = (self.width * frame_height * 4) as usize;
87
88        Self {
89            width: self.width,
90            height: frame_height,
91            pixels: self.pixels[..frame_size].to_vec(),
92            is_animated: false,
93            frame_count: 1,
94        }
95    }
96}
97
98/// Load a texture from PNG bytes.
99pub fn load_texture_from_bytes(data: &[u8]) -> Result<TextureData, image::ImageError> {
100    let img = image::load_from_memory(data)?;
101    let rgba = img.to_rgba8();
102    let (width, height) = rgba.dimensions();
103
104    // Check for animation (texture is taller than wide, height is multiple of width)
105    let is_animated = height > width && height % width == 0;
106    let frame_count = if is_animated { height / width } else { 1 };
107
108    Ok(TextureData {
109        width,
110        height,
111        pixels: rgba.into_raw(),
112        is_animated,
113        frame_count,
114    })
115}
116
117#[cfg(test)]
118mod tests {
119    use super::*;
120
121    #[test]
122    fn test_placeholder_texture() {
123        let tex = TextureData::placeholder();
124        assert_eq!(tex.width, 16);
125        assert_eq!(tex.height, 16);
126        assert_eq!(tex.pixels.len(), 16 * 16 * 4);
127        assert!(!tex.has_transparency());
128    }
129
130    #[test]
131    fn test_get_pixel() {
132        let tex = TextureData::new(2, 2, vec![255, 0, 0, 255, 0, 255, 0, 255, 0, 0, 255, 255, 255, 255, 255, 255]);
133
134        assert_eq!(tex.get_pixel(0, 0), [255, 0, 0, 255]); // Red
135        assert_eq!(tex.get_pixel(1, 0), [0, 255, 0, 255]); // Green
136        assert_eq!(tex.get_pixel(0, 1), [0, 0, 255, 255]); // Blue
137        assert_eq!(tex.get_pixel(1, 1), [255, 255, 255, 255]); // White
138    }
139
140    #[test]
141    fn test_has_transparency() {
142        let opaque = TextureData::new(1, 1, vec![255, 0, 0, 255]);
143        assert!(!opaque.has_transparency());
144
145        let transparent = TextureData::new(1, 1, vec![255, 0, 0, 128]);
146        assert!(transparent.has_transparency());
147    }
148}