Skip to main content

combs_mesh/blocks/
image.rs

1//! `img` — a sprite atlas of RGBA8 pixels with fixed-size frames.
2
3use serde::{Deserialize, Serialize};
4
5use crate::error::{MeshError, Result};
6
7/// An image block: an optional label plus the atlas itself.
8#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
9pub struct ImageBlock {
10    /// Optional label (may be empty).
11    #[serde(default)]
12    pub name: String,
13    /// The pixel data + frame geometry.
14    pub atlas: SpriteAtlas,
15}
16
17/// A sprite atlas: `width`×`height` RGBA8 pixels holding `frame_count`
18/// frames of `frame_width`×`frame_height`, laid out row-major.
19#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
20pub struct SpriteAtlas {
21    /// Atlas width in pixels.
22    pub width: u32,
23    /// Atlas height in pixels.
24    pub height: u32,
25    /// Frame width in pixels.
26    pub frame_width: u32,
27    /// Frame height in pixels.
28    pub frame_height: u32,
29    /// Number of frames in the atlas.
30    pub frame_count: u32,
31    /// RGBA8 pixels, `width * height * 4` bytes.
32    pub rgba: Vec<u8>,
33}
34
35impl SpriteAtlas {
36    /// Checks pixel length and frame geometry.
37    pub fn validate(&self) -> Result<()> {
38        let expected = self.width as usize * self.height as usize * 4;
39        if self.rgba.len() != expected {
40            return Err(MeshError::InvalidBlock(format!(
41                "atlas {}x{} expects {expected} RGBA bytes, got {}",
42                self.width,
43                self.height,
44                self.rgba.len()
45            )));
46        }
47        if self.frame_width == 0 || self.frame_height == 0 {
48            return Err(MeshError::InvalidBlock(
49                "frame dimensions must be non-zero".into(),
50            ));
51        }
52        if self.frame_width > self.width || self.frame_height > self.height {
53            return Err(MeshError::InvalidBlock(format!(
54                "frame {}x{} exceeds atlas {}x{}",
55                self.frame_width, self.frame_height, self.width, self.height
56            )));
57        }
58        let capacity = (self.width / self.frame_width) * (self.height / self.frame_height);
59        if self.frame_count == 0 || self.frame_count > capacity {
60            return Err(MeshError::InvalidBlock(format!(
61                "frame_count {} does not fit {} frames in atlas",
62                self.frame_count, capacity
63            )));
64        }
65        Ok(())
66    }
67}