1#![deny(non_upper_case_globals)]
7#![deny(non_camel_case_types)]
8#![deny(non_snake_case)]
9#![deny(unused_mut)]
10
11use std::io;
12use std::io::prelude::*;
13
14use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
15use flate2::read::GzDecoder;
16use flate2::write::GzEncoder;
17use flate2::Compression;
18
19use crate::prelude::XpColor;
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub struct XpCell {
24 pub ch: u32,
29 pub fg: XpColor,
31 pub bg: XpColor,
33}
34
35#[derive(Debug, Clone, PartialEq)]
38pub struct XpLayer {
39 pub width: usize,
41 pub height: usize,
43 pub cells: Vec<XpCell>,
45}
46
47impl XpLayer {
48 pub fn new(width: usize, height: usize) -> XpLayer {
51 XpLayer {
52 width,
53 height,
54 cells: vec![
55 XpCell {
56 ch: 0,
57 fg: XpColor::BLACK,
58 bg: XpColor::BLACK
59 };
60 width * height
61 ],
62 }
63 }
64
65 pub fn get(&self, x: usize, y: usize) -> Option<&XpCell> {
67 if x < self.width && y < self.height {
68 Some(&self.cells[x * self.height + y])
69 } else {
70 None
71 }
72 }
73
74 pub fn get_mut(&mut self, x: usize, y: usize) -> Option<&mut XpCell> {
76 if x < self.width && y < self.height {
77 Some(&mut self.cells[x * self.height + y])
78 } else {
79 None
80 }
81 }
82}
83
84#[derive(Debug, Clone, PartialEq)]
86pub struct XpFile {
87 pub version: i32,
89 pub layers: Vec<XpLayer>,
91}
92
93impl XpFile {
94 pub fn new(width: usize, height: usize) -> XpFile {
97 XpFile {
98 version: -1,
99 layers: vec![XpLayer::new(width, height)],
100 }
101 }
102
103 pub fn from_resource(path: &str) -> io::Result<XpFile> {
105 let res = bracket_embedding::prelude::EMBED
106 .lock()
107 .get_resource(path.to_string());
108 match res {
109 None => panic!("Unable to open resource"),
110 Some(r) => {
111 let buffer: Vec<u8> = Vec::from(r);
112 let mut bufslice = &*buffer;
113 XpFile::read(&mut bufslice)
114 }
115 }
116 }
117
118 pub fn read<R: Read>(f: &mut R) -> io::Result<XpFile> {
120 let mut rdr = GzDecoder::new(f);
121 let version = rdr.read_i32::<LittleEndian>()?;
122 let num_layers = rdr.read_u32::<LittleEndian>()?;
123
124 let mut layers = Vec::<XpLayer>::new();
125 layers.reserve(num_layers as usize);
126 for _ in 0..num_layers {
127 let width = rdr.read_u32::<LittleEndian>()? as usize;
128 let height = rdr.read_u32::<LittleEndian>()? as usize;
129
130 let mut cells = Vec::<XpCell>::new();
131 cells.reserve(width * height);
132 for _ in 0..width {
133 for _ in 0..height {
135 let ch = rdr.read_u32::<LittleEndian>()?;
136 let fg = XpColor::read(&mut rdr)?;
137 let bg = XpColor::read(&mut rdr)?;
138 cells.push(XpCell { ch, fg, bg });
139 }
140 }
141 layers.push(XpLayer {
142 width,
143 height,
144 cells,
145 });
146 }
147 Ok(XpFile { version, layers })
148 }
149
150 pub fn write<W: Write>(&self, f: &mut W) -> io::Result<()> {
152 let mut wr = GzEncoder::new(f, Compression::best());
153 wr.write_i32::<LittleEndian>(self.version)?; wr.write_u32::<LittleEndian>(self.layers.len() as u32)?;
155 for layer in &self.layers {
156 wr.write_u32::<LittleEndian>(layer.width as u32)?;
157 wr.write_u32::<LittleEndian>(layer.height as u32)?;
158
159 for cell in &layer.cells {
160 wr.write_u32::<LittleEndian>(cell.ch)?;
161 cell.fg.write(&mut wr)?;
162 cell.bg.write(&mut wr)?;
163 }
164 }
165 Ok(())
166 }
167}
168
169#[cfg(test)]
170mod tests {
171 use super::*;
172 use std::fs::File;
173 use std::io::{Cursor, Seek, SeekFrom};
174
175 const WIDTH: usize = 80;
176 const HEIGHT: usize = 60;
177
178 #[test]
179 fn test_roundtrip() {
180 let mut xp = XpFile::new(WIDTH, HEIGHT);
181 for y in 0..HEIGHT {
182 for x in 0..WIDTH {
183 let cell = xp.layers[0].get_mut(x, y).unwrap();
184 cell.ch = (32 + x + y) as u32;
185 cell.fg = XpColor::new(y as u8, 0, 255 - y as u8);
186 cell.bg = XpColor::new(x as u8, 0, 255 - x as u8);
187 }
188 }
189
190 let mut f = Cursor::new(Vec::new());
191 xp.write(&mut f).unwrap();
192 f.seek(SeekFrom::Start(0)).unwrap();
193
194 let xp2 = XpFile::read(&mut f).unwrap();
195 assert_eq!(xp, xp2);
196 }
197
198 #[test]
199 fn test_image() {
200 let mut f = File::open("../bracket-terminal/resources/mltest.xp").unwrap();
201 let xp = XpFile::read(&mut f).unwrap();
202 assert_eq!(xp.version, -1);
203 assert_eq!(xp.layers.len(), 2);
204 assert_eq!(xp.layers[0].width, 8);
205 assert_eq!(xp.layers[0].height, 4);
206 assert_eq!(xp.layers[1].width, 8);
207 assert_eq!(xp.layers[1].height, 4);
208 assert_eq!(xp.layers[1].get(0, 0).unwrap().fg, XpColor::BLACK);
209 assert_eq!(xp.layers[1].get(0, 0).unwrap().bg.is_transparent(), true);
210 assert_eq!(xp.layers[1].get(0, 0).unwrap().ch, 32);
211 assert_eq!(xp.layers[1].get(2, 2).unwrap().ch, 'B' as u32);
212 assert_eq!(xp.layers[0].get(0, 0).unwrap().fg, XpColor::new(0, 0, 255));
213 assert_eq!(xp.layers[0].get(0, 0).unwrap().bg, XpColor::BLACK);
214 assert_eq!(xp.layers[0].get(0, 0).unwrap().ch, 'A' as u32);
215 }
216}