Skip to main content

uorustlibs/
gump.rs

1//! Methods for reading data out of gumpart.mul and gumpidx.mul
2//!
3//! Gumps represent GUI elements, and actually use the opt fields in the index mul:
4//! `opt1` representing width, and `opt2` representing height
5//!
6//! The gump itself is stored as such:
7//! `|offsets:[u16..height]|rows:[row..height]|`
8//!
9//! The offsets are also used to calculate the length of a given row
10//!
11//! A row is defined as a number of RLE pairs:
12//!
13//! `|color:Color16|count:u16|`
14use crate::color::Color16;
15#[cfg(feature = "image")]
16use crate::color::{BLACK_16, Color};
17#[cfg(feature = "image")]
18use crate::error::ToImageError;
19use crate::error::{MulReaderError, MulReaderResult};
20use crate::mul::MulReader;
21use byteorder::{LittleEndian, ReadBytesExt};
22#[cfg(feature = "image")]
23use image::error::{DecodingError, ImageFormatHint};
24#[cfg(feature = "image")]
25use image::{ImageError, Rgba, RgbaImage};
26use std::fs::File;
27use std::io::{Cursor, Read, Seek, SeekFrom};
28use std::path::Path;
29
30/// An RLE pair
31#[derive(Debug, PartialEq, Eq, Clone, Copy)]
32pub struct GumpPair {
33    pub color: Color16,
34    pub count: u16,
35}
36
37/// A user-interface element.
38#[derive(Debug, PartialEq, Eq, Clone)]
39pub struct Gump {
40    pub width: u16,
41    pub height: u16,
42    /// Rows of RLE pairs
43    pub data: Vec<Vec<GumpPair>>,
44}
45
46#[cfg(feature = "image")]
47impl Gump {
48    /// Convert this asset into a standarized image format
49    pub fn to_image(&self) -> Result<RgbaImage, ImageError> {
50        let mut buffer = RgbaImage::new(self.width as u32, self.height as u32);
51        for (y, row) in self.data.iter().enumerate() {
52            let mut x = 0;
53            for run_pair in row {
54                // Check for overflow
55                if x + run_pair.count > self.width {
56                    return Err(ImageError::Decoding(DecodingError::new(
57                        ImageFormatHint::Name("UO Gump".to_string()),
58                        ToImageError::PixelOutOfBounds {
59                            x: (x + run_pair.count) as i64,
60                            y: y as i64,
61                        },
62                    )));
63                }
64
65                // Pure black is Transparent in Gumps
66                if run_pair.color != BLACK_16 {
67                    let (r, g, b, a) = run_pair.color.to_rgba();
68                    for i in 0..run_pair.count {
69                        buffer.put_pixel(x as u32 + i as u32, y as u32, Rgba([r, g, b, a]));
70                    }
71                }
72                x += run_pair.count;
73            }
74        }
75        Ok(buffer)
76    }
77}
78
79/// A struct to help read out Gump data
80#[derive(Debug)]
81pub struct GumpReader<T: Read + Seek> {
82    mul_reader: MulReader<T>,
83}
84
85impl GumpReader<File> {
86    /// Create a new GumpReader from an index and mul path
87    pub fn new(index_path: &Path, mul_path: &Path) -> MulReaderResult<GumpReader<File>> {
88        let mul_reader = MulReader::new(index_path, mul_path)?;
89        Ok(GumpReader { mul_reader })
90    }
91}
92
93impl<T: Read + Seek> GumpReader<T> {
94    /// Create a GumpReader from an existing mul reader
95    pub fn from_mul(reader: MulReader<T>) -> GumpReader<T> {
96        GumpReader { mul_reader: reader }
97    }
98
99    /// Read a single gump element
100    pub fn read(&mut self, index: u32) -> MulReaderResult<Gump> {
101        let raw = self.mul_reader.read(index)?;
102        let mut output = vec![];
103        let len = raw.data.len();
104
105        if len % 4 != 0 {
106            return Err(MulReaderError::UnexpectedSize {
107                found: len as u32,
108                expected: (len + (len % 4)) as u32,
109            });
110        }
111
112        let mut reader = Cursor::new(raw.data);
113        let mut row_offsets = vec![];
114        // Load all of our offsets. They're measured from the start of the file
115        for _i in 0..raw.opt1 {
116            row_offsets.push(reader.read_u32::<LittleEndian>()?);
117        }
118
119        // FIXME: The RLE stuff in here and in art should probably be abstracted
120        for (row_idx, offset) in row_offsets.iter().enumerate() {
121            let row_length = if row_idx == row_offsets.len() - 1 {
122                (len / 4) as u32 - offset
123            } else {
124                let next_row = row_offsets[row_idx + 1];
125                next_row - offset
126            };
127            reader.seek(SeekFrom::Start((*offset as u64) * 4))?;
128            let mut row = vec![];
129            for _i in 0..row_length {
130                let color = reader.read_u16::<LittleEndian>()?;
131                let count = reader.read_u16::<LittleEndian>()?;
132                row.push(GumpPair { color, count });
133            }
134            output.push(row);
135        }
136        Ok(Gump {
137            height: raw.opt1,
138            width: raw.opt2,
139            data: output,
140        })
141    }
142}