Skip to main content

uorustlibs/
art.rs

1//! Methods for reading tile and static data out of art.mul
2//!
3//! Tiles and Statics are both traditionally stored in art.mul/artidx.mul, and are packed into the
4//! same files - all entried before 0x4000 are tiles, and the rest statics.
5//!
6//! Map tiles are stored as
7//! `|header:u32|pixels:[u16..1022]|`
8//! Where pixels represents a list of rows, length 2, 4, 6, 8 .. 42, 44, 44, 42 .. 8, 6, 4, 2
9//!
10//! Statics are stored as
11//! `|size:u16|trigger:u16|width:u16|height:u16|offset_table:[u16..height]|rows:[row..height]`
12//!
13//! Rows are stored as
14//! `|runs:[RunPair..?]|`
15//! and are read until a stop value is found
16//!
17//! Run pairs are stored as
18//! `|x_offset:u16|run_length:u16|pixels:[Color16..run_length]|`
19//! where the x_offset defines how many transparent pixels should be left before drawing this run.
20//!
21//! A run pair with an offset and length of 0 denotes that the row is complete.
22#[cfg(feature = "image")]
23use crate::color::Color;
24use crate::color::Color16;
25use crate::error::{MEMWRITER_ERROR, MulReaderError, MulReaderResult};
26use crate::mul::MulReader;
27use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
28use std::fs::File;
29use std::io::{Cursor, Read, Seek, SeekFrom, Write};
30use std::path::Path;
31
32#[cfg(feature = "image")]
33use image::{Rgba, RgbaImage};
34
35/// A shared trait for dealing with both static and tile data
36pub trait Art {
37    /// Convert this asset back to the raw, storable form
38    fn serialize(&self) -> Vec<u8>;
39
40    /// Convert this asset into a standarized image format
41    #[cfg(feature = "image")]
42    fn to_image(&self) -> RgbaImage;
43}
44
45pub const TILE_SIZE: u32 = 2048;
46pub const STATIC_OFFSET: u32 = 0x4000;
47
48/// A run pair contains an offset at which point to start drawing the run,
49/// and the pixels to draw
50#[derive(Debug, PartialEq, Eq, Clone)]
51pub struct RunPair {
52    pub offset: u16,
53    pub run: Vec<Color16>,
54}
55
56impl RunPair {
57    fn serialize(&self) -> Vec<u8> {
58        let mut writer = vec![];
59
60        writer
61            .write_u16::<LittleEndian>(self.offset)
62            .expect(MEMWRITER_ERROR);
63        writer
64            .write_u16::<LittleEndian>(self.run.len() as u16)
65            .expect(MEMWRITER_ERROR);
66        for &color in self.run.iter() {
67            writer
68                .write_u16::<LittleEndian>(color)
69                .expect(MEMWRITER_ERROR);
70        }
71        writer
72    }
73}
74
75pub type StaticRow = Vec<RunPair>;
76
77/// A map tile, 44px by 44px.
78#[derive(Debug, PartialEq, Eq, Clone)]
79pub struct Tile {
80    /// Header information for a tile.  Unused
81    pub header: u32,
82    /// Individual pixels. These work as rows, of length 2, 4, 6, 8 .. 42, 44, 44, 42 .. 8, 6, 4, 2
83    /// These should be drawn centered to the 44px-wide tile
84    pub image_data: [Color16; 1022],
85}
86
87impl Art for Tile {
88    fn serialize(&self) -> Vec<u8> {
89        let mut writer = vec![];
90        writer
91            .write_u32::<LittleEndian>(self.header)
92            .expect(MEMWRITER_ERROR);
93        for &pixel in self.image_data.iter() {
94            writer
95                .write_u16::<LittleEndian>(pixel)
96                .expect(MEMWRITER_ERROR);
97        }
98        writer
99    }
100
101    #[cfg(feature = "image")]
102    fn to_image(&self) -> RgbaImage {
103        let mut buffer = RgbaImage::new(44, 44);
104        let mut read_idx = 0;
105
106        for y in 0..44 {
107            let slice_size = if y >= 22 { (44 - y) * 2 } else { (y + 1) * 2 };
108
109            let indent = 22 - (slice_size / 2);
110
111            for x in 0..slice_size {
112                let (r, g, b, a) = self.image_data[read_idx].to_rgba();
113                buffer.put_pixel(indent + x, y, Rgba([r, g, b, a]));
114                read_idx += 1;
115            }
116        }
117        buffer
118    }
119}
120
121impl Art for Static {
122    fn serialize(&self) -> Vec<u8> {
123        let mut writer = vec![];
124        writer
125            .write_u16::<LittleEndian>(self.size)
126            .expect(MEMWRITER_ERROR);
127        writer
128            .write_u16::<LittleEndian>(self.trigger)
129            .expect(MEMWRITER_ERROR);
130        writer
131            .write_u16::<LittleEndian>(self.width)
132            .expect(MEMWRITER_ERROR);
133        writer
134            .write_u16::<LittleEndian>(self.height)
135            .expect(MEMWRITER_ERROR);
136
137        let mut rows = vec![];
138
139        //Write our rows
140        for row in self.rows.iter() {
141            let mut out = vec![];
142            for pair in row.iter() {
143                out.write_all(pair.serialize().as_slice())
144                    .expect(MEMWRITER_ERROR);
145            }
146            //We write a "newline" after each out
147            out.write_u16::<LittleEndian>(0).expect(MEMWRITER_ERROR);
148            out.write_u16::<LittleEndian>(0).expect(MEMWRITER_ERROR);
149            rows.push(out);
150        }
151
152        let mut lookup_table = vec![];
153        let mut last_position = 0;
154        //Generate a lookup table
155        for row in rows.iter() {
156            lookup_table
157                .write_u16::<LittleEndian>(last_position)
158                .expect(MEMWRITER_ERROR);
159            last_position += (row.len() / 2) as u16;
160        }
161        writer
162            .write_all(lookup_table.as_slice())
163            .expect(MEMWRITER_ERROR);
164        for row in rows.iter() {
165            writer.write_all(row.as_slice()).expect(MEMWRITER_ERROR);
166        }
167
168        writer
169    }
170
171    #[cfg(feature = "image")]
172    fn to_image(&self) -> RgbaImage {
173        let mut buffer = RgbaImage::new(self.width as u32, self.height as u32);
174        for (y, row) in self.rows.iter().enumerate() {
175            let mut x: u32 = 0;
176            for run_pair in row.iter() {
177                x += run_pair.offset as u32;
178                for pixel in run_pair.run.iter() {
179                    let (r, g, b, a) = pixel.to_rgba();
180                    buffer.put_pixel(x, y as u32, Rgba([r, g, b, a]));
181                    x += 1;
182                }
183            }
184        }
185        buffer
186    }
187}
188
189/// A static image, typically used to render props
190#[derive(Debug, PartialEq, Eq, Clone)]
191pub struct Static {
192    /// The number of bytes this static is stored as
193    pub size: u16,
194    /// Trigger information. Yet unknown what this represents
195    pub trigger: u16,
196    /// The width of the image
197    pub width: u16,
198    /// The height of the image
199    pub height: u16,
200    /// The image data
201    pub rows: Vec<StaticRow>,
202}
203
204/// A struct to help read out Tile and Static data
205#[derive(Debug)]
206pub struct ArtReader<T: Read + Seek> {
207    mul_reader: MulReader<T>,
208}
209
210impl ArtReader<File> {
211    /// Create a new ArtReader from an index and mul path
212    pub fn new(index_path: &Path, mul_path: &Path) -> MulReaderResult<ArtReader<File>> {
213        let mul_reader = MulReader::new(index_path, mul_path)?;
214        Ok(ArtReader { mul_reader })
215    }
216}
217
218impl<T: Read + Seek> ArtReader<T> {
219    /// Create an ArtReader from an existing mul reader
220    pub fn from_mul(reader: MulReader<T>) -> ArtReader<T> {
221        ArtReader { mul_reader: reader }
222    }
223
224    /// Read a single tile
225    pub fn read_tile(&mut self, id: u32) -> MulReaderResult<Tile> {
226        if id >= STATIC_OFFSET {
227            return Err(MulReaderError::IndexOutOfBounds(id));
228        }
229
230        let raw = self.mul_reader.read(id)?;
231        let mut reader = Cursor::new(raw.data);
232
233        if raw.length > TILE_SIZE {
234            return Err(MulReaderError::UnexpectedSize {
235                found: raw.length,
236                expected: TILE_SIZE,
237            });
238        }
239
240        let header = reader.read_u32::<LittleEndian>()?;
241        let mut body = [0; 1022];
242        for cell in &mut body {
243            *cell = reader.read_u16::<LittleEndian>().unwrap_or(0);
244        }
245        Ok(Tile {
246            header,
247            image_data: body,
248        })
249    }
250
251    /// Read a single static.
252    ///
253    /// Statics are read with an offset, so 0 is the first static in the file.
254    pub fn read_static(&mut self, id: u32) -> MulReaderResult<Static> {
255        let offset_id = id + STATIC_OFFSET;
256
257        let raw = self.mul_reader.read(offset_id)?;
258        let mut reader = Cursor::new(raw.data);
259
260        let size = reader.read_u16::<LittleEndian>()?;
261        let trigger = reader.read_u16::<LittleEndian>()?;
262        let width = reader.read_u16::<LittleEndian>()?;
263        let height = reader.read_u16::<LittleEndian>()?;
264
265        if width == 0 || width >= 1024 || height == 0 || height >= 1024 {
266            return Err(MulReaderError::FailedParse(format!(
267                "Got invalid width and height of {}, {}",
268                width, height
269            )));
270        }
271
272        //Load our offset table
273        let mut offset_table = vec![];
274        for _index in 0..height {
275            offset_table.push(reader.read_u16::<LittleEndian>()?);
276        }
277
278        let data_start_pos = reader.position();
279        let mut rows = vec![];
280
281        for &offset in offset_table.iter() {
282            reader.seek(SeekFrom::Start(data_start_pos + offset as u64 * 2))?;
283            let mut row = vec![];
284
285            loop {
286                let x_offset = reader.read_u16::<LittleEndian>()?;
287                let run_length = reader.read_u16::<LittleEndian>()?;
288                if x_offset + run_length == 0 {
289                    break;
290                } else {
291                    let mut run = vec![];
292                    for _index in 0..run_length {
293                        run.push(reader.read_u16::<LittleEndian>()?);
294                    }
295
296                    row.push(RunPair {
297                        offset: x_offset,
298                        run,
299                    });
300                }
301            }
302            rows.push(row);
303        }
304
305        Ok(Static {
306            size,
307            trigger,
308            width,
309            height,
310            rows,
311        })
312    }
313}