1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
use std::io::Read;

use image::RgbaImage;

use crate::{
    cel::CelContent,
    reader::AseReader,
    tile::{self, Tile, EMPTY_TILE},
    AsepriteParseError, Cel, Result, Tileset,
};

/// A reference to a tilemap.
///
/// A tilemap describes an image as a collection of tiles from a [Tileset].
///
/// Every non-empty cel in a tilemap layer corresponds to one tilemap.
pub struct Tilemap<'a> {
    pub(crate) cel: Cel<'a>,
    pub(crate) tileset: &'a Tileset,
    pub(crate) logical_size: (u16, u16),
}

impl<'a> Tilemap<'a> {
    fn tilemap(&self) -> &TilemapData {
        if let CelContent::Tilemap(ref tilemap_data) = self.cel.raw_cel().unwrap().content {
            tilemap_data
        } else {
            panic!("Tilemap cel does not contain a tilemap")
        }
    }

    /// Width in number of tiles
    pub fn width(&self) -> u32 {
        self.logical_size.0 as u32
    }

    /// Height in number of tiles
    pub fn height(&self) -> u32 {
        self.logical_size.1 as u32
    }

    /// Width and height of each tile in the tilemap.
    pub fn tile_size(&self) -> (u32, u32) {
        let sz = self.tileset.tile_size();
        (sz.width() as u32, sz.height() as u32)
    }

    /// The tileset used by this tilemap.
    pub fn tileset(&self) -> &Tileset {
        self.tileset
    }

    /// The tilemap as one large image.
    pub fn image(&self) -> RgbaImage {
        self.cel.image()
    }

    /// Lookup tile at given location.
    ///
    /// Tile coordinates start at (0, 0) in the top left.
    ///
    /// Note: Aseprite as of 1.3-beta5 labels tile coordinates relative to the
    /// tile offsets. I.e., if your first column is empty, then the GUI shows
    /// `-1` for the x coordinate of the top-left tile.
    pub fn tile(&self, x: u32, y: u32) -> &Tile {
        let (ofs_x, ofs_y) = self.tile_offsets();
        let x = x as i32 - ofs_x;
        let y = y as i32 - ofs_y;
        // The actual tilemap data may be smaller because it does not include
        // any data for empty tiles on the outer rows or columns.
        let w = self.tilemap().width() as i32;
        let h = self.tilemap().height() as i32;
        if x < 0 || y < 0 || x >= w || y >= h {
            return &EMPTY_TILE;
        }
        let index = (y as usize * w as usize) + x as usize;
        &self.tilemap().tiles[index]
    }

    /// Describes first not-empty tile.
    pub fn tile_offsets(&self) -> (i32, i32) {
        let (x, y) = self.pixel_offsets();
        let size = self.tileset().tile_size();
        (x / size.width() as i32, y / size.height() as i32)
    }

    /// Describes first non-empty tile in pixel offsets.
    pub fn pixel_offsets(&self) -> (i32, i32) {
        self.cel.top_left()
    }
}

#[allow(unused)]
#[derive(Debug)]
pub struct TilemapData {
    width: u16,
    height: u16,
    //tileset_id: TilesetId,
    tiles: tile::Tiles,
    bits_per_tile: u16,
    bitmask_header: TileBitmaskHeader,
}

impl TilemapData {
    /// Width in number of tiles
    pub fn width(&self) -> u16 {
        self.width
    }

    /// Height in number of tiles
    pub fn height(&self) -> u16 {
        self.height
    }

    pub fn tile(&self, x: u16, y: u16) -> Option<&Tile> {
        if x >= self.width || y >= self.height {
            return None;
        }
        let index = (y as usize * self.width as usize) + x as usize;
        Some(&self.tiles[index])
    }

    pub(crate) fn parse_chunk<R: Read>(mut reader: AseReader<R>) -> Result<Self> {
        let width = reader.word()?;
        let height = reader.word()?;
        let bits_per_tile = reader.word()?;
        if bits_per_tile != 32 {
            return Err(AsepriteParseError::UnsupportedFeature(format!(
                "Asefile only supports 32 bits per tile, got input with {} bits per tile",
                bits_per_tile
            )));
        }
        let bitmask_header = TileBitmaskHeader::parse(&mut reader)?;
        reader.skip_reserved(10)?;
        let expected_tile_count = width as usize * height as usize;
        let tiles = tile::Tiles::unzip(reader, expected_tile_count, &bitmask_header)?;
        Ok(Self {
            width,
            height,
            tiles,
            bits_per_tile,
            bitmask_header,
        })
    }
}

#[derive(Debug)]
pub(crate) struct TileBitmaskHeader {
    pub tile_id: u32,
    pub x_flip: u32,
    pub y_flip: u32,
    pub rotate_90cw: u32,
}

impl TileBitmaskHeader {
    pub(crate) fn parse<R: Read>(reader: &mut AseReader<R>) -> Result<Self> {
        let tile_id = reader.dword()?;
        let x_flip = reader.dword()?;
        let y_flip = reader.dword()?;
        let rotate_90cw = reader.dword()?;
        Ok(Self {
            tile_id,
            x_flip,
            y_flip,
            rotate_90cw,
        })
    }
}