Skip to main content

uorustlibs/
anim.rs

1//! Methods for reading animated characters out of anim.mul/anim.idx
2//!
3//! Animations are stored as a sequence of frames, with offsets.
4//! Inside these frames, a sequence rows. Rows store their offsets, allowing for compact
5//! representations of the data
6//!
7//! The underlying raw data for AnimationGroup is defined as
8//! `|palette:[u16..256]|frame_count:u32|frame_offsets:[u32..frame_count]|frames:[frame..frame_count]|`
9//!
10//! Frame offsets are read from the end of the palette to find individual frames
11//!
12//! The raw frame is defined as
13//!
14//! `|image_center_x:i16|image_center_y:i16|width:u16|height:u16|rows:[row..?]|`
15//!
16//! Each row has a header, which either contains offset information and length, or a special stop
17//! value of `0x7FFF7FFF`
18//!
19//! `|header:u32|pixels:[u8..?]|`
20//!
21//!
22#[cfg(feature = "image")]
23use crate::color::Color;
24use crate::color::Color16;
25use crate::error::MulReaderResult;
26#[cfg(feature = "image")]
27use crate::error::ToImageError;
28use crate::mul::MulReader;
29use byteorder::{LittleEndian, ReadBytesExt};
30#[cfg(feature = "image")]
31use image::error::{DecodingError, ImageError, ImageFormatHint};
32#[cfg(feature = "image")]
33use image::{Delay, Frame, Frames, Rgba, RgbaImage};
34use std::fs::File;
35use std::io::{Cursor, Read, Seek, SeekFrom};
36use std::path::Path;
37#[cfg(feature = "image")]
38use std::time::Duration;
39
40const PALETTE_SIZE: usize = 256;
41const IMAGE_COMPLETE: u32 = 0x7FFF7FFF;
42
43const OFFSET_MASK: i32 = (0x200 << 22) | (0x200 << 12);
44
45/// A single row of a frame
46#[derive(Debug, PartialEq, Eq, Clone)]
47pub struct Row {
48    /// Compacted header information.
49    /// It contains the length of the associated data, plus offsets of where to plot, relative
50    /// to the image center
51    ///
52    /// `|x_offset..10bits|y_offset..10bits|run_length..12bits|`
53    ///
54    /// The offsets are signed values
55    pub header: u32,
56    /// Individual pixels for the row, as lookups in the AnimGroup palette
57    pub image_data: Vec<u8>,
58}
59
60impl Row {
61    /// Get the x offset of where to start drawing this row, relative to a center point
62    /// The resulting offset will be from the bottom left
63    pub fn x_offset(&self, image_center_x: i16) -> i32 {
64        (((self.header as i32 ^ OFFSET_MASK) >> 22) & 0x3FF) + image_center_x as i32 - 0x200
65    }
66
67    /// Get the y offset of where to start drawing this row, relative to a center point
68    /// The resulting offset will be from the bottom left
69    pub fn y_offset(&self, image_center_y: i16, height: u32) -> i32 {
70        (((self.header as i32 ^ OFFSET_MASK) >> 12) & 0x3FF) + image_center_y as i32 + height as i32
71            - 0x200
72    }
73
74    #[cfg(feature = "image")]
75    /// Draw a row into an image buffer.
76    ///
77    /// This is typically called by to_frame, and you won't need to call it directly
78    pub fn plot(
79        &self,
80        image_center_x: i16,
81        image_center_y: i16,
82        width: u16,
83        height: u16,
84        palette: &[u16],
85        buffer: &mut RgbaImage,
86    ) -> Result<(), ImageError> {
87        let x = self.x_offset(image_center_x);
88        let y = self.y_offset(image_center_y, height as u32);
89        if x < 0 || y < 0 || y >= height as i32 || x as u16 + self.image_data.len() as u16 > width {
90            return Err(ImageError::Decoding(DecodingError::new(
91                ImageFormatHint::Name("UO AnimFrame".to_string()),
92                ToImageError::PixelOutOfBounds {
93                    x: x as i64,
94                    y: y as i64,
95                },
96            )));
97        }
98        for i in 0..self.image_data.len() {
99            let target_x = x + i as i32;
100            if target_x > width as i32 {
101                return Err(ImageError::Decoding(DecodingError::new(
102                    ImageFormatHint::Name("UO AnimFrame".to_string()),
103                    ToImageError::PixelOutOfBounds {
104                        x: target_x as i64,
105                        y: y as i64,
106                    },
107                )));
108            }
109            let (r, g, b, a) = palette[self.image_data[i] as usize].to_rgba();
110            buffer.put_pixel(target_x as u32, y as u32, Rgba([r, g, b, a]));
111        }
112        Ok(())
113    }
114}
115
116/// A frame of an animtion
117#[derive(Debug, PartialEq, Eq, Clone)]
118pub struct AnimFrame {
119    pub image_center_x: i16,
120    pub image_center_y: i16,
121    pub width: u16,
122    pub height: u16,
123    pub data: Vec<Row>,
124}
125
126#[cfg(feature = "image")]
127impl AnimFrame {
128    /// Convert an individual frame to an Image frame
129    pub fn to_frame(&self, palette: &[u16]) -> Result<Frame, ImageError> {
130        if self.width == 0 || self.height == 0 {
131            return Err(ImageError::Decoding(DecodingError::from_format_hint(
132                ImageFormatHint::Name("UO AnimFrame".to_string()),
133            )));
134        }
135        let mut buffer = RgbaImage::new(self.width as u32, self.height as u32);
136        for row in &self.data {
137            row.plot(
138                self.image_center_x,
139                self.image_center_y,
140                self.width,
141                self.height,
142                palette,
143                &mut buffer,
144            )?;
145        }
146        Ok(Frame::from_parts(
147            buffer,
148            0,
149            0,
150            Delay::from_saturating_duration(Duration::from_millis(0)),
151        ))
152    }
153}
154
155/// An animation sequence
156#[derive(Debug, PartialEq, Eq, Clone)]
157pub struct AnimGroup {
158    pub palette: [Color16; 256],
159    pub frame_count: u32,
160    pub frames: Vec<AnimFrame>,
161}
162
163impl AnimGroup {
164    #[cfg(feature = "image")]
165    /// Convert an AnimGroup into Image-based frames.
166    ///
167    /// Currently, this doesn't produce values around delays
168    pub fn to_frames(&self) -> Frames<'_> {
169        Frames::new(Box::new(
170            self.frames
171                .iter()
172                .map(move |anim_frame| anim_frame.to_frame(&self.palette)),
173        ))
174    }
175}
176
177/// A struct to allow reading of animations from data muls
178#[derive(Debug)]
179pub struct AnimReader<T: Read + Seek> {
180    mul_reader: MulReader<T>,
181}
182
183fn read_frame<T: Read + Seek>(reader: &mut T) -> MulReaderResult<AnimFrame> {
184    let image_center_x = reader.read_i16::<LittleEndian>()?;
185    let image_center_y = reader.read_i16::<LittleEndian>()?;
186    let width = reader.read_u16::<LittleEndian>()?;
187    let height = reader.read_u16::<LittleEndian>()?;
188
189    let mut data = vec![];
190    loop {
191        let header = reader.read_u32::<LittleEndian>()?;
192        if header == IMAGE_COMPLETE {
193            break;
194        }
195        let run_length = header & 0xFFF;
196        let mut image_data = vec![];
197        for _i in 0..run_length {
198            image_data.push(reader.read_u8()?);
199        }
200        data.push(Row { header, image_data });
201    }
202
203    // Read data
204    Ok(AnimFrame {
205        image_center_x,
206        image_center_y,
207        width,
208        height,
209        data,
210    })
211}
212
213impl AnimReader<File> {
214    /// Create an animation reader from paths to an index mul and a data mul
215    pub fn new(index_path: &Path, mul_path: &Path) -> MulReaderResult<AnimReader<File>> {
216        let mul_reader = MulReader::new(index_path, mul_path)?;
217        Ok(AnimReader { mul_reader })
218    }
219}
220
221impl<T: Read + Seek> AnimReader<T> {
222    /// Create an animation reader from an existing Mul
223    pub fn from_mul(reader: MulReader<T>) -> AnimReader<T> {
224        AnimReader { mul_reader: reader }
225    }
226
227    /// Read an animation group by id
228    pub fn read(&mut self, id: u32) -> MulReaderResult<AnimGroup> {
229        let raw = self.mul_reader.read(id)?;
230        let mut reader = Cursor::new(raw.data);
231        // Read the palette
232        let mut palette = [0; PALETTE_SIZE];
233        for cell in &mut palette {
234            *cell = reader.read_u16::<LittleEndian>()?;
235        }
236
237        let frame_count = reader.read_u32::<LittleEndian>()?;
238        let mut frame_offsets = vec![];
239        for _ in 0..frame_count {
240            frame_offsets.push(reader.read_u32::<LittleEndian>()?);
241        }
242
243        let mut frames = vec![];
244        for offset in frame_offsets {
245            reader.seek(SeekFrom::Start((PALETTE_SIZE as u32 * 2 + offset) as u64))?;
246            frames.push(read_frame(&mut reader)?);
247        }
248
249        Ok(AnimGroup {
250            palette,
251            frame_count,
252            frames,
253        })
254    }
255}