use crate::color::Color; use serde::{Deserialize, Serialize}; pub const GLYPH_BANK_PALETTE_COUNT_V1: usize = 64; pub const GLYPH_BANK_COLORS_PER_PALETTE: usize = 16; /// Standard sizes for square tiles. #[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize)] pub enum TileSize { /// 8x8 pixels. Size8 = 8, /// 16x16 pixels. Size16 = 16, /// 32x32 pixels. Size32 = 32, } /// A container for graphical assets. /// /// A GlyphBank stores the decoded runtime representation of a glyph-bank asset. /// /// Serialized `assets.pa` payloads keep pixel indices packed as `4bpp` nibbles. /// After decode, the runtime expands them to one `u8` palette index per pixel /// while preserving the same `0..15` logical range. pub struct GlyphBank { /// Dimension of each individual tile in the bank. pub tile_size: TileSize, /// Width of the full bank sheet in pixels. pub width: usize, /// Height of the full bank sheet in pixels. pub height: usize, /// Decoded pixel data stored as one palette index per pixel. /// Serialized payloads are packed; runtime memory is expanded. /// Palette indices are ordinary indices; transparency is resolved through /// the RGBA alpha channel of the palette entry. pub pixel_indices: Vec, /// Runtime-facing v1 palette table: 64 palettes of 16 RGBA8888 colors each. pub palettes: [[Color; GLYPH_BANK_COLORS_PER_PALETTE]; GLYPH_BANK_PALETTE_COUNT_V1], } impl GlyphBank { /// Creates an empty glyph bank with the specified dimensions. pub fn new(tile_size: TileSize, width: usize, height: usize) -> Self { Self { tile_size, width, height, pixel_indices: vec![0; width * height], palettes: [[Color::BLACK; GLYPH_BANK_COLORS_PER_PALETTE]; GLYPH_BANK_PALETTE_COUNT_V1], } } /// Resolves a global tile ID and local pixel coordinates to a palette index. /// tile_id: the tile index in the bank /// local_x, local_y: the pixel position inside the tile (0 to tile_size-1) pub fn get_pixel_index(&self, tile_id: u16, local_x: usize, local_y: usize) -> u8 { let size = self.tile_size as usize; let tiles_per_row = self.width / size; let tile_x = (tile_id as usize % tiles_per_row) * size; let tile_y = (tile_id as usize / tiles_per_row) * size; let pixel_x = tile_x + local_x; let pixel_y = tile_y + local_y; if pixel_x < self.width && pixel_y < self.height { self.pixel_indices[pixel_y * self.width + pixel_x] } else { 0 } } /// Maps a 4-bit index to a real RGBA8888 Color using the specified palette. pub fn resolve_color(&self, palette_id: u8, pixel_index: u8) -> Color { self.palettes .get(palette_id as usize) .and_then(|palette| palette.get(pixel_index as usize)) .copied() .unwrap_or(Color::TRANSPARENT) } }