ici_files/
palette.rs

1use crate::errors::IndexedImageError::*;
2use crate::palette::FilePalette::*;
3use crate::prelude::*;
4use std::collections::HashSet;
5
6pub(crate) const PAL_NO_DATA: u8 = 0;
7pub(crate) const PAL_ID: u8 = 1;
8pub(crate) const PAL_NAME: u8 = 2;
9pub(crate) const PAL_COLORS: u8 = 3;
10
11/// How palette data is stored in an ICI file
12#[derive(Debug, Clone, Eq, PartialEq)]
13pub enum FilePalette {
14    /// Include no palette information
15    NoData,
16    /// Include palette id (reader will need to know what the id refers to)
17    ID(u16),
18    /// Include palette name (reader will need to know what the name refers to) 1..=255 chars
19    Name(String),
20    /// Include palette colors
21    Colors,
22}
23
24fn distinct_count(colors: &[Color]) -> usize {
25    colors.iter().collect::<HashSet<_>>().len()
26}
27
28/// merges similar colors until there are < `max` unique colors
29/// the result will contain duplicates so the index is preserved
30pub fn simplify_palette_to_fit(colors: &[Color], max: usize) -> Vec<Color> {
31    let mut output = colors.to_vec();
32    let mut threshold = 2;
33    while distinct_count(&output) >= max {
34        output = simplify_palette(&output, threshold);
35        threshold += 10;
36    }
37    output
38}
39
40/// merges similar colors that fall with threshold of each other
41/// will remove gradients if threshold is too high or gradient too smooth
42///
43/// recommend starting threshold at 2, max is 1020
44/// the result will contain duplicates so the index is preserved
45pub fn simplify_palette(colors: &[Color], threshold: usize) -> Vec<Color> {
46    let mut output = colors.to_vec();
47    let mut idx = 0;
48
49    loop {
50        let color = &output[idx];
51        let mut to_merge = None;
52        for (i, cmp_color) in output.iter().enumerate() {
53            let diff = color.diff(cmp_color);
54            if idx != i && diff > 0 && diff < threshold {
55                to_merge = Some(i);
56                break;
57            }
58        }
59        if let Some(i) = to_merge {
60            let c = output[idx].mid(&output[i]);
61            output[idx] = c;
62            output[i] = c;
63        } else {
64            idx += 1;
65            if idx >= output.len() {
66                break;
67            }
68        }
69    }
70
71    output
72}
73
74impl FilePalette {
75    pub(crate) fn to_byte(&self) -> u8 {
76        match self {
77            NoData => PAL_NO_DATA,
78            ID(_) => PAL_ID,
79            Name(_) => PAL_NAME,
80            Colors => PAL_COLORS,
81        }
82    }
83}
84
85pub(crate) fn write(
86    palette: &FilePalette,
87    colors: &[Color],
88    output: &mut Vec<u8>,
89) -> Result<(), IndexedImageError> {
90    output.push(palette.to_byte());
91    match palette {
92        NoData => {}
93        ID(id) => output.extend_from_slice(&id.to_be_bytes()),
94        Name(name) => {
95            let len = name.len();
96            if len < 1 {
97                return Err(PaletteNameTooShort);
98            }
99            if len > 255 {
100                return Err(PaletteNameTooLong);
101            }
102            output.push(len as u8);
103            output.extend_from_slice(name.as_bytes())
104        }
105        Colors => {
106            output.push(colors.len() as u8);
107            for color in colors {
108                output.push(color.r);
109                output.push(color.g);
110                output.push(color.b);
111                output.push(color.a);
112            }
113        }
114    }
115
116    Ok(())
117}
118
119pub(crate) fn read(
120    mut start_idx: usize,
121    bytes: &[u8],
122) -> Result<(usize, FilePalette, Option<Vec<Color>>), IndexedImageError> {
123    if bytes.len() <= start_idx {
124        return Err(InvalidFileFormat(
125            start_idx,
126            "No data after header, expected palette format".to_string(),
127        ));
128    }
129    let pal_type = bytes[start_idx];
130    start_idx += 1;
131    match pal_type {
132        PAL_NO_DATA => Ok((1, NoData, None)),
133        PAL_ID => {
134            if bytes.len() < start_idx + 1 {
135                return Err(InvalidFileFormat(
136                    start_idx,
137                    "No data after palette format, expected ID".to_string(),
138                ));
139            }
140            let bytes = &bytes[start_idx..=start_idx + 1];
141            let id = u16::from_be_bytes([bytes[0], bytes[1]]);
142            Ok((3, ID(id), None))
143        }
144        PAL_NAME => {
145            if bytes.len() < start_idx {
146                return Err(InvalidFileFormat(
147                    start_idx,
148                    "No data after palette format, expected palette name length".to_string(),
149                ));
150            }
151            let len = bytes[start_idx];
152            start_idx += 1;
153            let end = len as usize;
154            if bytes.len() < start_idx + end {
155                return Err(InvalidFileFormat(
156                    start_idx,
157                    "Incomplete data after palette name length, expected palette name".to_string(),
158                ));
159            }
160            let name = String::from_utf8(bytes[start_idx..start_idx + end].to_vec())
161                .map_err(PaletteNameNotUtf8)?;
162            Ok((end + 2, Name(name), None))
163        }
164        PAL_COLORS => {
165            if bytes.len() < start_idx {
166                return Err(InvalidFileFormat(
167                    start_idx,
168                    "No data after palette format, expected color count".to_string(),
169                ));
170            }
171            let count = bytes[start_idx];
172            start_idx += 1;
173            let end = count as usize * 4;
174            if bytes.len() < start_idx + end {
175                return Err(InvalidFileFormat(
176                    start_idx,
177                    format!("Incomplete data after palette color count, expected {count} colors"),
178                ));
179            }
180            let mut colors = vec![];
181            let color_bytes: Vec<&u8> = bytes.iter().skip(start_idx).take(end).collect();
182            for color in color_bytes.chunks_exact(4) {
183                colors.push(Color::new(*color[0], *color[1], *color[2], *color[3]));
184            }
185            Ok((end + 2, Colors, Some(colors)))
186        }
187        _ => Err(InvalidFileFormat(
188            start_idx,
189            format!("Unsupport palette type {pal_type}"),
190        )),
191    }
192}
193
194#[cfg(test)]
195mod test {
196    use super::*;
197
198    #[test]
199    fn write_no_data() {
200        let mut output = vec![];
201        write(&NoData, &[], &mut output).unwrap();
202        assert_eq!(output, vec![PAL_NO_DATA]);
203
204        let mut output = vec![];
205        write(&NoData, &[Color::new(255, 45, 231, 2)], &mut output).unwrap();
206        assert_eq!(output, vec![PAL_NO_DATA]);
207    }
208
209    #[test]
210    fn write_id() {
211        let mut output = vec![];
212        write(&ID(5), &[], &mut output).unwrap();
213        assert_eq!(output, vec![PAL_ID, 0, 5]);
214
215        let mut output = vec![];
216        write(&ID(256), &[Color::new(255, 45, 231, 2)], &mut output).unwrap();
217        assert_eq!(output, vec![PAL_ID, 1, 0]);
218    }
219
220    #[test]
221    fn write_name() {
222        let mut output = vec![];
223        write(&Name("test".to_string()), &[], &mut output).unwrap();
224        assert_eq!(output, vec![PAL_NAME, 4, b't', b'e', b's', b't']);
225
226        let mut output = vec![];
227        write(
228            &Name("😺".to_string()),
229            &[Color::new(255, 45, 231, 2)],
230            &mut output,
231        )
232        .unwrap();
233        assert_eq!(output, vec![PAL_NAME, 4, 240, 159, 152, 186]);
234    }
235
236    #[test]
237    fn write_colors() {
238        let mut output = vec![];
239        write(&Colors, &[Color::new(100, 101, 102, 103)], &mut output).unwrap();
240        assert_eq!(output, vec![PAL_COLORS, 1, 100, 101, 102, 103]);
241
242        let mut output = vec![];
243        write(
244            &Colors,
245            &[Color::new(100, 101, 102, 103), Color::new(0, 0, 0, 255)],
246            &mut output,
247        )
248        .unwrap();
249        assert_eq!(
250            output,
251            vec![PAL_COLORS, 2, 100, 101, 102, 103, 0, 0, 0, 255]
252        );
253    }
254
255    #[test]
256    fn read_no_data() {
257        let (skip, pal_type, colors) = read(0, &[PAL_NO_DATA]).unwrap();
258        assert_eq!(skip, 1);
259        assert_eq!(pal_type, NoData);
260        assert_eq!(colors, None);
261    }
262
263    #[test]
264    fn read_id() {
265        let (skip, pal_type, colors) = read(0, &[PAL_ID, 0, 5]).unwrap();
266        assert_eq!(skip, 3);
267        assert_eq!(pal_type, ID(5));
268        assert_eq!(colors, None);
269    }
270
271    #[test]
272    fn read_name() {
273        let (skip, pal_type, colors) = read(0, &[PAL_NAME, 4, 240, 159, 152, 186]).unwrap();
274        assert_eq!(skip, 6);
275        assert_eq!(pal_type, Name("😺".to_string()));
276        assert_eq!(colors, None);
277    }
278
279    #[test]
280    fn read_colors() {
281        let (skip, pal_type, colors) =
282            read(0, &[PAL_COLORS, 2, 100, 101, 102, 103, 0, 0, 0, 255]).unwrap();
283        assert_eq!(skip, 10);
284        assert_eq!(pal_type, Colors);
285        assert_eq!(
286            colors,
287            Some(vec![
288                Color::new(100, 101, 102, 103),
289                Color::new(0, 0, 0, 255)
290            ])
291        );
292    }
293
294    #[test]
295    fn write_data_before() {
296        let mut output = vec![1, 1, 1, 1];
297        write(&ID(5), &[], &mut output).unwrap();
298        assert_eq!(output, vec![1, 1, 1, 1, PAL_ID, 0, 5]);
299    }
300
301    #[test]
302    fn read_data_either_side() {
303        let bytes = [
304            1, 1, 1, 1, PAL_COLORS, 2, 100, 101, 102, 103, 0, 0, 0, 255, 2, 2, 2, 2,
305        ];
306        let start = 4;
307        let (skip, pal_type, colors) = read(start, &bytes).unwrap();
308        assert_eq!(skip, 10);
309        assert_eq!(pal_type, Colors);
310        assert_eq!(
311            colors,
312            Some(vec![
313                Color::new(100, 101, 102, 103),
314                Color::new(0, 0, 0, 255)
315            ])
316        );
317        assert_eq!(bytes[start + skip..], [2, 2, 2, 2]);
318    }
319}