elma_lgr/
lib.rs

1//! Crate for parsing ElastoMania LGR files.
2//!
3//! LGR files contain PCX images.
4//!
5//! Example usage:
6//!
7//!     use elma_lgr::Lgr;
8//!
9//!     let lgr = Lgr::load_from_file("lgr/example.lgr", false, false).unwrap();
10//!     println!("There are {} images in this LGR file", lgr.images.len());
11//!     for (name, image) in lgr.images {
12//!         println!("{}, width = {}, height = {}", name, image.width, image.height);
13//!     }
14//!
15extern crate byteorder;
16extern crate pcx;
17
18use std::{io, iter};
19use std::path::Path;
20use std::fs::File;
21use std::collections::BTreeMap;
22use byteorder::{LittleEndian, ReadBytesExt};
23
24/// Kind of a picture.
25#[derive(Debug, Copy, Clone, PartialEq, Eq)]
26pub enum PictureKind {
27    /// A simple picture with the top-left pixel signing the transparent color.
28    Picture,
29
30    /// Texture.
31    Texture,
32
33    /// Masks are used to draw textures.
34    Mask,
35}
36
37/// Clipping property.
38#[derive(Debug, Copy, Clone, PartialEq, Eq)]
39pub enum ClippingMode {
40    /// `Unclipped` means that the picture or texture will be seen above the whole picture (unless other pictures hide it).
41    Unclipped,
42
43    /// `Ground` means the picture will be seen only above the ground.
44    Ground,
45
46    /// `Sky` means the picture will be seen only above the sky.
47    Sky,
48}
49
50/// Picture description.
51#[derive(Debug, Clone, Copy)]
52pub struct Picture {
53    /// Whether this is picture, texture or mask.
54    pub kind : PictureKind,
55
56    /// The distance must be in the 1-999 range. If a picture has less distance than an other, it will hide the other picture.
57    /// The motorbiker and the food-exit-killer objects have a distance of 500.
58    pub distance : u32,
59
60    /// The clipping property determines whether a picture is seen above the sky, the ground, or both (this is independent of the distances).
61    pub clipping : ClippingMode,
62}
63
64/// Image from LGR.
65#[derive(Debug, Clone)]
66pub struct Image {
67    /// Optional information describing image.
68    pub info : Option<Picture>,
69
70    /// Width of the image.
71    pub width : u16,
72
73    /// Height of the image.
74    pub height : u16,
75
76    /// Image pixels. Each pixel is an index into LGR palette.
77    ///
78    /// This array will only contain values if LGR was loaded with `load_pixels` option.
79    pub pixels : Vec<u8>,
80
81    /// Raw content of the PCX file.
82    ///
83    /// This array will only contain values if LGR was loaded with `load_raw_pcx` option.
84    pub pcx : Vec<u8>,
85}
86
87/// Content of an LGR file.
88#[derive(Debug, Clone)]
89pub struct Lgr {
90    /// All images contained in this LGR. Names include .pcx extension.
91    pub images : BTreeMap <String, Image>,
92
93    /// Palette contains 256-colors, format is R0, G0, B0, R1, G1, B1, ...
94    pub palette : Vec<u8>,
95}
96
97impl Image {
98    /// Get image pixel. Returned value is an index into LGR palette.
99    ///
100    /// This function will panic if `x >= width`, `y >= height` or LGR was loaded with `load_pixels` set to `false`.
101    #[inline]
102    pub fn get_pixel(&self, x : u16, y : u16) -> u8 {
103        self.pixels[(y as usize)*(self.width as usize) + (x as usize)]
104    }
105}
106
107impl Lgr {
108    /// Get color from LGR palette. There are 256 colors in the palette.
109    ///
110    /// Returns triple (R, G, B).
111    #[inline]
112    pub fn get_palette_color(&self, i : u8) -> (u8, u8, u8) {
113        (
114            self.palette[(i as usize)*3],
115            self.palette[(i as usize)*3 + 1],
116            self.palette[(i as usize)*3 + 2]
117        )
118    }
119
120    /// Read LGR from file.
121    ///
122    /// Arguments:
123    ///
124    /// * `load_pixels` - load pixels from PCX images and store them into `Image::pixels`.
125    /// * `load_raw_pcx` - load raw byte content of PCX images into `Image::pcx`.
126    ///
127    /// If you are going to use LGR for rendering then set `load_pixels` to `true` and `load_raw_pcx` to `false`.
128    /// If you wat to extract files from LGR then set `load_pixels` to `false` and `load_raw_pcx` to `true`.
129    pub fn load_from_file<P: AsRef<Path>>(path: P, load_pixels: bool, load_raw_pcx: bool) -> io::Result<Self> {
130        let file = File::open(path)?;
131        Self::load_from_stream(&mut io::BufReader::new(file), load_pixels, load_raw_pcx)
132    }
133
134    /// Read LGR from stream.
135    ///
136    /// See description of `load_from_file` for more info.
137    pub fn load_from_stream<R : io::Read>(stream : &mut R, load_pixels: bool, load_raw_pcx: bool) -> io::Result<Self> {
138        let mut magic  = [0; 5];
139        stream.read_exact(&mut magic)?;
140
141        if &magic != b"LGR12" {
142            return error("Not an LGR");
143        }
144
145        let total_images = stream.read_u32::<LittleEndian>()? as usize;
146        let unknown = stream.read_u32::<LittleEndian>()?;
147        if unknown != 1002 { // some kind of version or something like that
148            return error("LGR: invalid unknown value != 1002");
149        }
150
151        let listed_images = stream.read_u32::<LittleEndian>()? as usize;
152
153        let mut infos = read_pictures(stream, listed_images)?;
154
155        let mut images = BTreeMap::new();
156        let mut palette = Vec::new();
157        for _ in 0..total_images {
158            let name = read_string(stream, 12)?;
159            let _unknown_a = stream.read_i32::<LittleEndian>()?;
160            let _unknown_b = stream.read_i32::<LittleEndian>()?;
161            let length = stream.read_u32::<LittleEndian>()? as usize;
162
163            let mut pcx : Vec<u8> = std::iter::repeat(0).take(length).collect();
164            stream.read_exact(&mut pcx)?;
165
166            let info = infos.remove(name.trim_right_matches(".pcx"));
167
168            let (pixels, width, height) = {
169                let mut pcx_reader = pcx::Reader::new(&pcx[..])?;
170                let (width, height) = (pcx_reader.width() as usize, pcx_reader.height() as usize);
171
172                let pixels = if load_pixels {
173                    let mut pixels: Vec<u8> = iter::repeat(0).take(width*height).collect();
174
175                    for i in 0..height {
176                        pcx_reader.next_row_paletted(&mut pixels[i*width..(i + 1)*width])?;
177                    }
178
179                    pixels
180                } else {
181                    Vec::new()
182                };
183
184                // Masks contain invalid palettes, we can take palette from the first image that is not a mask.
185                let valid_palette = if let Some(info) = info {
186                    info.kind != PictureKind::Mask
187                } else {
188                    true
189                };
190
191                if valid_palette && palette.is_empty() {
192                    palette = iter::repeat(0).take(256*3).collect();
193                    pcx_reader.read_palette(&mut palette)?;
194                }
195
196                (pixels, width as u16, height as u16)
197            };
198
199            if !load_raw_pcx {
200                pcx.clear();
201                pcx.shrink_to_fit();
202            }
203
204            images.insert(name, Image {
205                info : info,
206                width : width,
207                height : height,
208                pixels : pixels,
209                pcx : pcx,
210            });
211        }
212
213        Ok(Lgr {
214            images : images,
215            palette : palette,
216        })
217    }
218}
219
220fn trim_at_zero(string : &mut Vec<u8>) {
221    for i in 0..string.len() {
222        if string[i] == 0 {
223            string.truncate(i);
224            break;
225        }
226    }
227}
228
229fn error<T>(msg : &str) -> io::Result<T> {
230    Err(io::Error::new(io::ErrorKind::InvalidData, msg))
231}
232
233fn read_string<R : io::Read>(stream : &mut R, len : usize) -> io::Result<String> {
234    let mut string : Vec<u8> = std::iter::repeat(0).take(len).collect();
235    stream.read_exact(&mut string)?;
236
237    trim_at_zero(&mut string);
238
239    match String::from_utf8(string) {
240        Ok(s) => Ok(s),
241        Err(_) => error("LGR: invalid ASCII"),
242    }
243}
244
245fn read_pictures<R : io::Read>(stream : &mut R, listed_images : usize) -> io::Result<BTreeMap<String, Picture>> {
246    // need tp init array to something, these values will be overwritten
247    let initial_info = Picture {
248        kind : PictureKind::Picture,
249        distance : 0,
250        clipping : ClippingMode::Unclipped,
251    };
252    let mut pictures : Vec<(String, Picture)> = std::iter::repeat(("".to_string(), initial_info)).take(listed_images).collect();
253
254    for i in 0..listed_images {
255        pictures[i].0 = read_string(stream, 10)?;
256    }
257
258    for i in 0..listed_images {
259        let kind = stream.read_u32::<LittleEndian>()?;
260        pictures[i].1.kind = match kind {
261            100 => PictureKind::Picture,
262            101 => PictureKind::Texture,
263            102 => PictureKind::Mask,
264            _ => return error("LGR: unknown picture kind"),
265        };
266    }
267
268    for i in 0..listed_images {
269        pictures[i].1.distance = stream.read_u32::<LittleEndian>()?;
270    }
271
272    for i in 0..listed_images {
273        let clipping = stream.read_u32::<LittleEndian>()?;
274        pictures[i].1.clipping = match clipping {
275            0 => ClippingMode::Unclipped,
276            1 => ClippingMode::Ground,
277            2 => ClippingMode::Sky,
278            _ => return error("LGR: unknown clipping mode"),
279        };
280    }
281
282    for _ in 0..listed_images {
283        let unknown = stream.read_u32::<LittleEndian>()?;
284        if unknown != 12 {
285            return error("LGR: invalid unknown value != 12");
286        }
287    }
288
289    Ok(pictures.into_iter().collect())
290}
291
292#[cfg(test)]
293mod tests {
294    use Lgr;
295
296    #[test]
297    fn load() {
298        let lgr = Lgr::load_from_file("lgr/example.lgr", false, false).unwrap();
299        assert_eq!(lgr.images.len(), 77);
300
301        let lgr = Lgr::load_from_file("lgr/example.lgr", false, true).unwrap();
302        assert_eq!(lgr.images.len(), 77);
303
304        let lgr = Lgr::load_from_file("lgr/example.lgr", true, false).unwrap();
305        assert_eq!(lgr.images.len(), 77);
306
307        let lgr = Lgr::load_from_file("lgr/example.lgr", true, true).unwrap();
308        assert_eq!(lgr.images.len(), 77);
309    }
310}