1#![deny(missing_debug_implementations)]
7#![deny(non_upper_case_globals)]
8#![deny(non_camel_case_types)]
9#![deny(non_snake_case)]
10#![deny(unused_mut)]
11#![warn(missing_docs)]
12
13use std::io;
14use std::io::prelude::*;
15
16use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
17use flate2::read::GzDecoder;
18use flate2::write::GzEncoder;
19use flate2::Compression;
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub struct XpColor {
24 pub r: u8,
26 pub g: u8,
28 pub b: u8,
30}
31
32impl XpColor {
33 pub const BLACK: XpColor = XpColor { r: 0, g: 0, b: 0 };
35 pub const TRANSPARENT: XpColor = XpColor {
37 r: 255,
38 g: 0,
39 b: 255,
40 };
41
42 pub fn new(r: u8, g: u8, b: u8) -> XpColor {
44 XpColor { r, g, b }
45 }
46
47 pub fn is_transparent(self) -> bool {
50 self == XpColor::TRANSPARENT
51 }
52
53 fn read<T: ReadBytesExt>(rdr: &mut T) -> io::Result<XpColor> {
55 let r = rdr.read_u8()?;
56 let g = rdr.read_u8()?;
57 let b = rdr.read_u8()?;
58 Ok(XpColor { r, g, b })
59 }
60
61 fn write<T: WriteBytesExt>(self, wr: &mut T) -> io::Result<()> {
63 wr.write_u8(self.r)?;
64 wr.write_u8(self.g)?;
65 wr.write_u8(self.b)?;
66 Ok(())
67 }
68}
69
70#[derive(Debug, Clone, Copy, PartialEq, Eq)]
72pub struct XpCell {
73 pub ch: u32,
78 pub fg: XpColor,
80 pub bg: XpColor,
82}
83
84#[derive(Debug, Clone, PartialEq)]
87pub struct XpLayer {
88 pub width: usize,
90 pub height: usize,
92 pub cells: Vec<XpCell>,
94}
95
96impl XpLayer {
97 pub fn new(width: usize, height: usize) -> XpLayer {
100 XpLayer {
101 width,
102 height,
103 cells: vec![
104 XpCell {
105 ch: 0,
106 fg: XpColor::BLACK,
107 bg: XpColor::BLACK
108 };
109 width * height
110 ],
111 }
112 }
113
114 pub fn get(&self, x: usize, y: usize) -> Option<&XpCell> {
116 if x < self.width && y < self.height {
117 Some(&self.cells[x * self.height + y])
118 } else {
119 None
120 }
121 }
122
123 pub fn get_mut(&mut self, x: usize, y: usize) -> Option<&mut XpCell> {
125 if x < self.width && y < self.height {
126 Some(&mut self.cells[x * self.height + y])
127 } else {
128 None
129 }
130 }
131}
132
133#[derive(Debug, Clone, PartialEq)]
135pub struct XpFile {
136 pub version: i32,
138 pub layers: Vec<XpLayer>,
140}
141
142impl XpFile {
143 pub fn new(width: usize, height: usize) -> XpFile {
146 XpFile {
147 version: -1,
148 layers: vec![XpLayer::new(width, height)],
149 }
150 }
151
152 pub fn read<R: Read>(f: &mut R) -> io::Result<XpFile> {
154 let mut rdr = GzDecoder::new(f);
155 let version = rdr.read_i32::<LittleEndian>()?;
156 let num_layers = rdr.read_u32::<LittleEndian>()?;
157
158 let mut layers = Vec::<XpLayer>::new();
159 layers.reserve(num_layers as usize);
160 for _layer in 0..num_layers {
161 let width = rdr.read_u32::<LittleEndian>()? as usize;
162 let height = rdr.read_u32::<LittleEndian>()? as usize;
163
164 let mut cells = Vec::<XpCell>::new();
165 cells.reserve(width * height);
166 for _y in 0..width {
167 for _x in 0..height {
169 let ch = rdr.read_u32::<LittleEndian>()?;
170 let fg = XpColor::read(&mut rdr)?;
171 let bg = XpColor::read(&mut rdr)?;
172 cells.push(XpCell { ch, fg, bg });
173 }
174 }
175 layers.push(XpLayer {
176 width,
177 height,
178 cells,
179 });
180 }
181 Ok(XpFile { version, layers })
182 }
183
184 pub fn write<W: Write>(&self, f: &mut W) -> io::Result<()> {
186 let mut wr = GzEncoder::new(f, Compression::best());
187 wr.write_i32::<LittleEndian>(self.version)?; wr.write_u32::<LittleEndian>(self.layers.len() as u32)?;
189 for layer in &self.layers {
190 wr.write_u32::<LittleEndian>(layer.width as u32)?;
191 wr.write_u32::<LittleEndian>(layer.height as u32)?;
192
193 for cell in &layer.cells {
194 wr.write_u32::<LittleEndian>(cell.ch)?;
195 cell.fg.write(&mut wr)?;
196 cell.bg.write(&mut wr)?;
197 }
198 }
199 Ok(())
200 }
201}
202
203#[cfg(test)]
204mod tests {
205 use super::*;
206 use std::fs::File;
207 use std::io::{Cursor, Seek, SeekFrom};
208
209 const WIDTH: usize = 80;
210 const HEIGHT: usize = 60;
211
212 #[test]
213 fn test_roundtrip() {
214 let mut xp = XpFile::new(WIDTH, HEIGHT);
215 for y in 0..HEIGHT {
216 for x in 0..WIDTH {
217 let cell = xp.layers[0].get_mut(x, y).unwrap();
218 cell.ch = (32 + x + y) as u32;
219 cell.fg = XpColor::new(y as u8, 0, 255 - y as u8);
220 cell.bg = XpColor::new(x as u8, 0, 255 - x as u8);
221 }
222 }
223
224 let mut f = Cursor::new(Vec::new());
225 xp.write(&mut f).unwrap();
226 f.seek(SeekFrom::Start(0)).unwrap();
227
228 let xp2 = XpFile::read(&mut f).unwrap();
229 assert_eq!(xp, xp2);
230 }
231
232 #[test]
233 fn test_image() {
234 let mut f = File::open("test_images/mltest.xp").unwrap();
235 let xp = XpFile::read(&mut f).unwrap();
236 assert_eq!(xp.version, -1);
237 assert_eq!(xp.layers.len(), 2);
238 assert_eq!(xp.layers[0].width, 8);
239 assert_eq!(xp.layers[0].height, 4);
240 assert_eq!(xp.layers[1].width, 8);
241 assert_eq!(xp.layers[1].height, 4);
242 assert_eq!(xp.layers[1].get(0, 0).unwrap().fg, XpColor::BLACK);
243 assert_eq!(xp.layers[1].get(0, 0).unwrap().bg.is_transparent(), true);
244 assert_eq!(xp.layers[1].get(0, 0).unwrap().ch, 32);
245 assert_eq!(xp.layers[1].get(2, 2).unwrap().ch, 'B' as u32);
246 assert_eq!(xp.layers[0].get(0, 0).unwrap().fg, XpColor::new(0, 0, 255));
247 assert_eq!(xp.layers[0].get(0, 0).unwrap().bg, XpColor::BLACK);
248 assert_eq!(xp.layers[0].get(0, 0).unwrap().ch, 'A' as u32);
249 }
250}