1use crate::file_utils::{convert_vec, ReaderExt};
2use crate::read_write_impl::{FileReadable, Readable, Writeable};
3use crate::GameFileError;
4use crate::GameFileError::{FileAccessError, InvalidPalette};
5use std::path::Path;
6
7const PALETTE_HEADER: [u8; 2] = [0xFD, 0xA2];
8pub const PALETTE_EXT: &str = "mpal";
9
10#[derive(Clone, Eq, PartialEq, Debug)]
11pub struct Palette {
12 pub filepath: Option<String>,
13 pub colors: [Color; 16],
14}
15
16impl Palette {
17 pub fn new(filepath: Option<String>, palette: [Color; 16]) -> Self {
18 Self {
19 filepath,
20 colors: palette,
21 }
22 }
23}
24
25impl Palette {
26 pub fn filename(&self) -> Option<String> {
27 self.filepath.as_ref().and_then(|path| {
28 Path::new(&path)
29 .file_name()
30 .map(|name| name.to_string_lossy().to_string())
31 })
32 }
33}
34
35#[derive(Copy, Clone, Eq, PartialEq, Debug)]
36pub struct Color {
37 pub r: u8,
38 pub g: u8,
39 pub b: u8,
40}
41
42impl Color {
43 pub fn new(r: u8, g: u8, b: u8) -> Self {
44 Self { r, g, b }
45 }
46
47 pub fn from_bytes(rgb: [u8; 3]) -> Color {
48 Color::new(rgb[0], rgb[1], rgb[2])
49 }
50}
51
52impl Color {
53 pub fn as_bytes(&self) -> [u8; 3] {
54 [self.r, self.g, self.b]
55 }
56}
57
58impl Writeable for Palette {
59 fn as_bytes(&self) -> Result<Vec<u8>, GameFileError> {
60 let mut output = vec![];
61 output.extend_from_slice(&PALETTE_HEADER);
62 for color in self.colors {
63 output.extend_from_slice(&color.as_bytes());
64 }
65 Ok(output)
66 }
67}
68
69impl Readable for Palette {
70 fn from_reader<R: ReaderExt>(reader: &mut R) -> Result<Self, GameFileError>
71 where
72 Self: Sized,
73 {
74 let mut header = [0; 2];
75 reader
76 .read_exact(&mut header)
77 .map_err(|e| FileAccessError(e, "reading palette header data"))?;
78 if header != PALETTE_HEADER {
79 return Err(InvalidPalette(String::from("Not a palette file")));
80 }
81 let blocks = reader
82 .read_multiple_blocks(3, 16)
83 .map_err(|e| FileAccessError(e, "reading palette data"))?;
84 let colours: Vec<Color> = blocks
85 .into_iter()
86 .map(|rgb| Color::from_bytes(convert_vec(rgb)))
87 .collect();
88 Ok(Palette::new(None, convert_vec(colours)))
89 }
90}
91
92impl FileReadable for Palette {}
93
94#[cfg(test)]
95mod test {
96 use super::*;
97 use std::io::BufReader;
98
99 #[test]
100 fn header_check() {
101 let data: Vec<u8> = vec![0, 0];
102 let palette = Palette::from_reader(&mut BufReader::new(&*data));
103 assert!(palette.is_err());
104 assert_eq!(
105 palette.err().unwrap().to_string(),
106 String::from("Invalid Palette file: Not a palette file")
107 )
108 }
109
110 #[test]
111 fn read() {
112 let data: Vec<u8> = vec![
113 PALETTE_HEADER[0],
114 PALETTE_HEADER[1],
115 0,
116 0,
117 0,
118 255,
119 255,
120 255,
121 100,
122 100,
123 100,
124 101,
125 101,
126 101,
127 102,
128 102,
129 102,
130 103,
131 104,
132 105,
133 99,
134 88,
135 77,
136 1,
137 2,
138 3,
139 4,
140 5,
141 6,
142 7,
143 8,
144 9,
145 11,
146 22,
147 33,
148 66,
149 55,
150 44,
151 88,
152 77,
153 99,
154 1,
155 10,
156 100,
157 2,
158 20,
159 200,
160 158,
161 158,
162 158,
163 ];
164 let palette = Palette::from_reader(&mut BufReader::new(&*data)).unwrap();
165 assert_eq!(palette.filepath, None);
166 assert_eq!(palette.colors[0], Color::new(0, 0, 0));
167 assert_eq!(palette.colors[1], Color::new(255, 255, 255));
168 assert_eq!(palette.colors[2], Color::new(100, 100, 100));
169 assert_eq!(palette.colors[3], Color::new(101, 101, 101));
170 assert_eq!(palette.colors[4], Color::new(102, 102, 102));
171 assert_eq!(palette.colors[5], Color::new(103, 104, 105));
172 assert_eq!(palette.colors[6], Color::new(99, 88, 77));
173 assert_eq!(palette.colors[7], Color::new(1, 2, 3));
174 assert_eq!(palette.colors[8], Color::new(4, 5, 6));
175 assert_eq!(palette.colors[9], Color::new(7, 8, 9));
176 assert_eq!(palette.colors[10], Color::new(11, 22, 33));
177 assert_eq!(palette.colors[11], Color::new(66, 55, 44));
178 assert_eq!(palette.colors[12], Color::new(88, 77, 99));
179 assert_eq!(palette.colors[13], Color::new(1, 10, 100));
180 assert_eq!(palette.colors[14], Color::new(2, 20, 200));
181 assert_eq!(palette.colors[15], Color::new(158, 158, 158));
182 }
183}