mabel_aseprite/
tilemap.rs

1use std::io::Read;
2
3use image::RgbaImage;
4
5use crate::{
6    cel::CelContent,
7    reader::AseReader,
8    tile::{self, Tile, EMPTY_TILE},
9    AsepriteParseError, Cel, Result, Tileset,
10};
11
12/// A reference to a tilemap.
13///
14/// A tilemap describes an image as a collection of tiles from a [Tileset].
15///
16/// Every non-empty cel in a tilemap layer corresponds to one tilemap.
17pub struct Tilemap<'a> {
18    pub(crate) cel: Cel<'a>,
19    pub(crate) tileset: &'a Tileset,
20    pub(crate) logical_size: (u16, u16),
21}
22
23impl<'a> Tilemap<'a> {
24    fn tilemap(&self) -> &TilemapData {
25        if let CelContent::Tilemap(ref tilemap_data) = self.cel.raw_cel().unwrap().content {
26            tilemap_data
27        } else {
28            panic!("Tilemap cel does not contain a tilemap")
29        }
30    }
31
32    /// Width in number of tiles
33    pub fn width(&self) -> u32 {
34        self.logical_size.0 as u32
35    }
36
37    /// Height in number of tiles
38    pub fn height(&self) -> u32 {
39        self.logical_size.1 as u32
40    }
41
42    /// Width and height of each tile in the tilemap.
43    pub fn tile_size(&self) -> (u32, u32) {
44        let sz = self.tileset.tile_size();
45        (sz.width() as u32, sz.height() as u32)
46    }
47
48    /// The tileset used by this tilemap.
49    pub fn tileset(&self) -> &Tileset {
50        self.tileset
51    }
52
53    /// The tilemap as one large image.
54    pub fn image(&self) -> RgbaImage {
55        self.cel.image()
56    }
57
58    /// Lookup tile at given location.
59    ///
60    /// Tile coordinates start at (0, 0) in the top left.
61    ///
62    /// Note: Aseprite as of 1.3-beta5 labels tile coordinates relative to the
63    /// tile offsets. I.e., if your first column is empty, then the GUI shows
64    /// `-1` for the x coordinate of the top-left tile.
65    pub fn tile(&self, x: u32, y: u32) -> &Tile {
66        let (ofs_x, ofs_y) = self.tile_offsets();
67        let x = x as i32 - ofs_x;
68        let y = y as i32 - ofs_y;
69        // The actual tilemap data may be smaller because it does not include
70        // any data for empty tiles on the outer rows or columns.
71        let w = self.tilemap().width() as i32;
72        let h = self.tilemap().height() as i32;
73        if x < 0 || y < 0 || x >= w || y >= h {
74            return &EMPTY_TILE;
75        }
76        let index = (y as usize * w as usize) + x as usize;
77        &self.tilemap().tiles[index]
78    }
79
80    /// Describes first not-empty tile.
81    pub fn tile_offsets(&self) -> (i32, i32) {
82        let (x, y) = self.pixel_offsets();
83        let size = self.tileset().tile_size();
84        (x / size.width() as i32, y / size.height() as i32)
85    }
86
87    /// Describes first non-empty tile in pixel offsets.
88    pub fn pixel_offsets(&self) -> (i32, i32) {
89        self.cel.top_left()
90    }
91}
92
93#[allow(unused)]
94#[derive(Debug)]
95pub struct TilemapData {
96    width: u16,
97    height: u16,
98    //tileset_id: TilesetId,
99    tiles: tile::Tiles,
100    bits_per_tile: u16,
101    bitmask_header: TileBitmaskHeader,
102}
103
104impl TilemapData {
105    /// Width in number of tiles
106    pub fn width(&self) -> u16 {
107        self.width
108    }
109
110    /// Height in number of tiles
111    pub fn height(&self) -> u16 {
112        self.height
113    }
114
115    pub fn tile(&self, x: u16, y: u16) -> Option<&Tile> {
116        if x >= self.width || y >= self.height {
117            return None;
118        }
119        let index = (y as usize * self.width as usize) + x as usize;
120        Some(&self.tiles[index])
121    }
122
123    pub(crate) fn parse_chunk<R: Read>(mut reader: AseReader<R>) -> Result<Self> {
124        let width = reader.word()?;
125        let height = reader.word()?;
126        let bits_per_tile = reader.word()?;
127        if bits_per_tile != 32 {
128            return Err(AsepriteParseError::UnsupportedFeature(format!(
129                "Asefile only supports 32 bits per tile, got input with {} bits per tile",
130                bits_per_tile
131            )));
132        }
133        let bitmask_header = TileBitmaskHeader::parse(&mut reader)?;
134        reader.skip_reserved(10)?;
135        let expected_tile_count = width as usize * height as usize;
136        let tiles = tile::Tiles::unzip(reader, expected_tile_count, &bitmask_header)?;
137        Ok(Self {
138            width,
139            height,
140            tiles,
141            bits_per_tile,
142            bitmask_header,
143        })
144    }
145}
146
147#[derive(Debug)]
148pub(crate) struct TileBitmaskHeader {
149    pub tile_id: u32,
150    pub x_flip: u32,
151    pub y_flip: u32,
152    pub rotate_90cw: u32,
153}
154
155impl TileBitmaskHeader {
156    pub(crate) fn parse<R: Read>(reader: &mut AseReader<R>) -> Result<Self> {
157        let tile_id = reader.dword()?;
158        let x_flip = reader.dword()?;
159        let y_flip = reader.dword()?;
160        let rotate_90cw = reader.dword()?;
161        Ok(Self {
162            tile_id,
163            x_flip,
164            y_flip,
165            rotate_90cw,
166        })
167    }
168}