mabel_aseprite/
palette.rs1use crate::{reader::AseReader, AsepriteParseError, Result};
2use nohash::IntMap;
3
4#[derive(Debug)]
6pub struct ColorPalette {
7 pub(crate) entries: IntMap<u32, ColorPaletteEntry>,
9}
10
11#[derive(Debug)]
13pub struct ColorPaletteEntry {
14 id: u32,
15 rgba8: [u8; 4],
16 name: Option<String>,
17}
18
19impl ColorPalette {
20 pub fn num_colors(&self) -> u32 {
22 self.entries.len() as u32
23 }
24
25 pub fn color(&self, index: u32) -> Option<&ColorPaletteEntry> {
31 self.entries.get(&index)
32 }
33
34 pub(crate) fn validate_indexed_pixels(&self, indexed_pixels: &[u8]) -> Result<()> {
35 for pixel in indexed_pixels {
40 let color = self.color(*pixel as u32);
41 color.ok_or_else(|| {
42 AsepriteParseError::InvalidInput(format!("Palette index invalid: {}", pixel,))
43 })?;
44 }
45 Ok(())
46 }
47}
48
49impl ColorPaletteEntry {
50 pub fn id(&self) -> u32 {
52 self.id
53 }
54
55 pub fn raw_rgba8(&self) -> [u8; 4] {
58 self.rgba8
59 }
60
61 pub fn red(&self) -> u8 {
63 self.rgba8[0]
64 }
65
66 pub fn green(&self) -> u8 {
68 self.rgba8[1]
69 }
70
71 pub fn blue(&self) -> u8 {
73 self.rgba8[2]
74 }
75
76 pub fn alpha(&self) -> u8 {
78 self.rgba8[3]
79 }
80
81 pub fn name(&self) -> Option<&str> {
83 self.name.as_deref()
84 }
85}
86
87pub(crate) fn parse_chunk(data: &[u8]) -> Result<ColorPalette> {
88 let mut reader = AseReader::new(data);
89
90 let _num_total_entries = reader.dword()?;
91 let first_color_index = reader.dword()?;
92 let last_color_index = reader.dword()?;
93 reader.skip_reserved(8)?;
94
95 if last_color_index < first_color_index {
96 return Err(AsepriteParseError::InvalidInput(format!(
97 "Bad palette color indices: first={} last={}",
98 first_color_index, last_color_index,
99 )));
100 }
101
102 let count = last_color_index - first_color_index + 1;
103 let mut entries = IntMap::default();
105
106 for id in 0..count {
107 let flags = reader.word()?;
108 let red = reader.byte()?;
109 let green = reader.byte()?;
110 let blue = reader.byte()?;
111 let alpha = reader.byte()?;
112 let name = if flags & 1 == 1 {
113 let s = reader.string()?;
114 Some(s)
115 } else {
116 None
117 };
118 let id = id + first_color_index;
119 entries.insert(
120 id,
121 ColorPaletteEntry {
122 id,
123 rgba8: [red, green, blue, alpha],
124 name,
125 },
126 );
127 }
128
129 Ok(ColorPalette { entries })
130}
131
132fn scale_6bit_to_8bit(color: u8) -> Result<u8> {
135 if color >= 64 {
136 return Err(AsepriteParseError::InvalidInput(format!(
137 "6-bit color outside range: {}",
138 color
139 )));
140 }
141
142 Ok(color << 2 | color >> 4)
152}
153
154pub(crate) fn parse_old_chunk_04(data: &[u8]) -> Result<ColorPalette> {
155 let mut reader = AseReader::new(data);
156
157 let packet_count = reader.word()?;
158
159 let mut entries = IntMap::default();
160 let mut skip = 0;
161
162 for _ in 0..packet_count {
163 skip += reader.byte()? as u32;
164 let mut count = reader.byte()? as u32;
165
166 if count == 0 {
167 count = 256;
168 }
169
170 count += skip;
171 for id in skip..count {
172 let red = reader.byte()?;
173 let green = reader.byte()?;
174 let blue = reader.byte()?;
175 entries.insert(
176 id,
177 ColorPaletteEntry {
178 id,
179 rgba8: [red, green, blue, 255],
180 name: None,
181 },
182 );
183 }
184 }
185
186 Ok(ColorPalette { entries })
187}
188
189pub(crate) fn parse_old_chunk_11(data: &[u8]) -> Result<ColorPalette> {
190 let mut reader = AseReader::new(data);
191
192 let packet_count = reader.word()?;
193
194 let mut entries = IntMap::default();
195 let mut skip = 0;
196
197 for _ in 0..packet_count {
198 skip += reader.byte()? as u32;
199 let mut count = reader.byte()? as u32;
200
201 if count == 0 {
202 count = 256;
203 }
204
205 count += skip;
206 for id in skip..count {
207 let red = scale_6bit_to_8bit(reader.byte()?)?;
208 let green = scale_6bit_to_8bit(reader.byte()?)?;
209 let blue = scale_6bit_to_8bit(reader.byte()?)?;
210 entries.insert(
211 id,
212 ColorPaletteEntry {
213 id,
214 rgba8: [red, green, blue, 255],
215 name: None,
216 },
217 );
218 }
219 }
220
221 Ok(ColorPalette { entries })
222}