Skip to main content

theframework/
thepalette.rs

1pub use crate::prelude::*;
2use std::ops::{Index, IndexMut};
3
4/// Holds an array of colors.
5#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
6pub struct ThePalette {
7    #[serde(default)]
8    pub current_index: u16,
9    pub colors: Vec<Option<TheColor>>,
10}
11
12impl Default for ThePalette {
13    fn default() -> Self {
14        Self::empty_256()
15    }
16}
17
18impl ThePalette {
19    pub fn new(colors: Vec<Option<TheColor>>) -> Self {
20        Self {
21            current_index: 0,
22            colors,
23        }
24    }
25
26    pub fn empty_256() -> Self {
27        let mut colors = Vec::new();
28        for _ in 0..256 {
29            colors.push(None);
30        }
31        Self {
32            current_index: 0,
33            colors,
34        }
35    }
36
37    /// Test if the palette is empty
38    pub fn is_empty(&self) -> bool {
39        for v in self.colors.iter() {
40            if v.is_some() {
41                return false;
42            }
43        }
44        true
45    }
46
47    /// Get the color at the current index
48    pub fn get_current_color(&self) -> Option<TheColor> {
49        self.colors[self.current_index as usize].clone()
50    }
51
52    /// Clears all palette colors.
53    pub fn clear(&mut self) {
54        for v in self.colors.iter_mut() {
55            *v = None;
56        }
57    }
58
59    /// Load the palette from a Paint.net TXT file
60    pub fn load_from_txt(&mut self, txt: String) {
61        let mut index = self.current_index as usize;
62        for line in txt.lines() {
63            // Ignore comments
64            if line.starts_with(';') {
65                continue;
66            }
67
68            let mut chars = line.chars();
69
70            // Skip Alpha
71            if chars.next().is_none() {
72                return;
73            }
74            if chars.next().is_none() {
75                return;
76            }
77
78            // R
79            let mut r_string = "".to_string();
80            if let Some(c) = chars.next() {
81                r_string.push(c);
82            }
83            if let Some(c) = chars.next() {
84                r_string.push(c);
85            }
86
87            let r = u8::from_str_radix(&r_string, 16);
88
89            // G
90            let mut g_string = "".to_string();
91            if let Some(c) = chars.next() {
92                g_string.push(c);
93            }
94            if let Some(c) = chars.next() {
95                g_string.push(c);
96            }
97
98            let g = u8::from_str_radix(&g_string, 16);
99
100            // B
101            let mut b_string = "".to_string();
102            if let Some(c) = chars.next() {
103                b_string.push(c);
104            }
105            if let Some(c) = chars.next() {
106                b_string.push(c);
107            }
108
109            let b = u8::from_str_radix(&b_string, 16);
110
111            if r.is_ok() && g.is_ok() && b.is_ok() {
112                let r = r.ok().unwrap();
113                let g = g.ok().unwrap();
114                let b = b.ok().unwrap();
115
116                if index < self.colors.len() {
117                    self.colors[index] = Some(TheColor::from_u8(r, g, b, 0xFF));
118                }
119
120                index += 1;
121            }
122        }
123    }
124
125    /// Adds a color to the palette if it doesn't already exist.
126    /// Returns the index where the color exists or was inserted.
127    pub fn add_unique_color(&mut self, color: TheColor) -> Option<usize> {
128        // Check if color already exists
129        for (i, existing) in self.colors.iter().enumerate() {
130            if let Some(existing_color) = existing {
131                if *existing_color == color {
132                    return Some(i);
133                }
134            }
135        }
136
137        // Try to insert into the first empty slot
138        for (i, slot) in self.colors.iter_mut().enumerate() {
139            if slot.is_none() {
140                *slot = Some(color);
141                return Some(i);
142            }
143        }
144
145        // Palette is full
146        None
147    }
148
149    /*
150    /// Returns the index of the closest matching color in the palette.
151    /// Returns `None` if the palette is empty.
152    pub fn find_closest_color_index(&self, color: &TheColor) -> Option<usize> {
153        let mut best_index = None;
154        let mut best_distance = f32::MAX;
155
156        for (i, entry) in self.colors.iter().enumerate() {
157            if let Some(existing) = entry {
158                // Compute squared Euclidean distance in RGBA space
159                let dr = existing.r - color.r;
160                let dg = existing.g - color.g;
161                let db = existing.b - color.b;
162                let da = existing.a - color.a;
163
164                let dist_sq = dr * dr + dg * dg + db * db + da * da;
165
166                if dist_sq < best_distance {
167                    best_distance = dist_sq;
168                    best_index = Some(i);
169                }
170            }
171        }
172
173        best_index
174    }*/
175
176    /// Returns the index of the palette color that best matches the given color.
177    /// Used for palette remapping (closest-color quantization).
178    /// Returns `None` if the palette is empty.
179    pub fn find_closest_color_index(&self, color: &TheColor) -> Option<usize> {
180        let mut best_index = None;
181        let mut best_distance = f32::MAX;
182
183        for (i, entry) in self.colors.iter().enumerate() {
184            if let Some(existing) = entry {
185                // Perceptual weighted distance in linear RGBA space
186                let dr = existing.r - color.r;
187                let dg = existing.g - color.g;
188                let db = existing.b - color.b;
189                let da = existing.a - color.a;
190
191                // Human-vision–weighted RGB, alpha has lower influence
192                let dist = dr * dr * 0.30 + dg * dg * 0.59 + db * db * 0.11 + da * da * 0.05;
193
194                if dist < best_distance {
195                    best_distance = dist;
196                    best_index = Some(i);
197                }
198            }
199        }
200
201        best_index
202    }
203}
204
205impl Index<usize> for ThePalette {
206    type Output = Option<TheColor>;
207
208    fn index(&self, index: usize) -> &Self::Output {
209        if index < self.colors.len() {
210            &self.colors[index]
211        } else {
212            panic!("Color Index out of bounds!");
213        }
214    }
215}
216
217impl IndexMut<usize> for ThePalette {
218    fn index_mut(&mut self, index: usize) -> &mut Self::Output {
219        if index < self.colors.len() {
220            &mut self.colors[index]
221        } else {
222            panic!("Color Index out of bounds!");
223        }
224    }
225}