1use color_utils::{calculate_distance, RGB};
70use core::f64;
71use std::{
72 cmp::Ordering,
73 collections::HashMap,
74 sync::mpsc::channel,
75 thread::{self},
76};
77mod color_utils;
78
79pub use color_utils::calculate_color_from_panes as calculate_color_from_panes_custom;
81
82pub use color_utils::PreciseRGB;
84
85#[allow(dead_code)]
87#[derive(Debug, Clone)]
88#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
89pub struct Panes {
90 panes: Vec<String>,
91 distance: f64,
92 color: PreciseRGB,
93}
94
95impl PartialEq for Panes {
96 fn eq(&self, other: &Self) -> bool {
97 self.distance == other.distance
98 }
99}
100
101impl Eq for Panes {}
102
103impl PartialOrd for Panes {
104 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
105 Some(self.cmp(other))
106 }
107}
108
109impl Ord for Panes {
110 fn cmp(&self, other: &Self) -> Ordering {
111 self.distance.partial_cmp(&other.distance).unwrap()
112 }
113}
114impl Panes {
115 fn from_panes_vec(
116 panes: &[String],
117 available_colors: &HashMap<String, RGB>,
118 target: RGB,
119 ) -> Self {
120 let color = calculate_color_from_panes_custom(panes, available_colors);
121 let dist = calculate_distance(color, target);
122 Self {
123 panes: panes.to_vec(),
124 distance: dist,
125 color,
126 }
127 }
128}
129pub fn get_standard_colors() -> HashMap<String, [u8; 3]> {
131 HashMap::from([
132 ("white".to_string(), [249, 255, 254]),
133 ("orange".to_string(), [249, 128, 29]),
134 ("magenta".to_string(), [199, 78, 189]),
135 ("light_blue".to_string(), [58, 179, 218]),
136 ("yellow".to_string(), [254, 216, 61]),
137 ("lime".to_string(), [128, 199, 31]),
138 ("pink".to_string(), [243, 139, 170]),
139 ("gray".to_string(), [71, 79, 82]),
140 ("light_gray".to_string(), [157, 157, 151]),
141 ("cyan".to_string(), [22, 156, 156]),
142 ("purple".to_string(), [137, 50, 184]),
143 ("blue".to_string(), [60, 68, 170]),
144 ("brown".to_string(), [131, 84, 50]),
145 ("green".to_string(), [94, 124, 22]),
146 ("red".to_string(), [176, 46, 38]),
147 ("black".to_string(), [29, 29, 33]),
148 ])
149}
150
151pub fn calculate_color_from_panes_default(panes: &[String]) -> PreciseRGB {
153 calculate_color_from_panes_custom(panes, &convert_colors(&get_standard_colors()))
154}
155
156#[allow(clippy::implicit_hasher)]
158pub fn find_combination_custom_colors(
159 color: [u8; 3],
160 colors: &HashMap<String, [u8; 3]>,
161 depth: u8,
162 cutoff: u8,
163) -> Option<Panes> {
164 find_combination(color, &convert_colors(colors), depth, cutoff)
165}
166
167pub fn find_combination_custom(color: [u8; 3], depth: u8, cutoff: u8) -> Option<Panes> {
169 find_combination(
170 color,
171 &convert_colors(&get_standard_colors()),
172 depth,
173 cutoff,
174 )
175}
176
177pub fn find_combination_default(color: [u8; 3]) -> Option<Panes> {
179 let depth = 6;
180 let cutoff = 6;
181 find_combination(
182 color,
183 &convert_colors(&get_standard_colors()),
184 depth,
185 cutoff,
186 )
187}
188
189fn convert_colors(colors: &HashMap<String, [u8; 3]>) -> HashMap<String, RGB> {
190 let mut new_colors = HashMap::new();
191
192 for x in colors {
193 new_colors.insert(x.0.clone(), RGB::new(*x.1));
194 }
195 new_colors
196}
197
198#[allow(clippy::implicit_hasher)]
199fn find_combination(
200 color: [u8; 3],
201 colors: &HashMap<String, RGB>,
202 depth: u8,
203 cutoff: u8,
204) -> Option<Panes> {
205 let color = RGB::new(color);
206 if depth == 0 {
207 return None;
208 }
209 if usize::from(cutoff) >= colors.len() {
210 return None;
211 }
212 let mut all = calculate_combinations_recursively(colors, 0, depth, color, cutoff, &Vec::new());
213 all.sort();
214 Some(all[0].clone())
216}
217
218fn calculate_combinations_recursively(
219 colors: &HashMap<String, RGB>,
220 depth: u8,
221 max_depth: u8,
222 target: RGB,
223 cutoff: u8,
224 base_panes: &[String],
225) -> Vec<Panes> {
226 if depth == max_depth {
227 return vec![Panes::from_panes_vec(base_panes, colors, target)];
228 }
229
230 let mut possibilities = Vec::new();
231
232 let dists = get_distances(base_panes, colors, target);
233 let trimmed_dists = drop_entries(&dists, cutoff);
234 for dist in trimmed_dists {
235 let mut possibility: Vec<String> = base_panes.to_vec();
236 possibility.push(dist.0);
237 possibilities.push(possibility);
238 }
239
240 let mut collection = Vec::new();
241 for possibility in possibilities {
242 collection.extend(calculate_combinations_recursively(
243 colors,
244 depth + 1,
245 max_depth,
246 target,
247 cutoff,
248 &possibility,
249 ));
250 }
251 collection
252}
253
254fn drop_entries(distance_pairs: &[(String, f64)], cutoff: u8) -> Vec<(String, f64)> {
255 let mut distance_pairs = distance_pairs.to_owned();
256 distance_pairs.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal));
257 distance_pairs
258 .into_iter()
259 .take(usize::from(cutoff))
260 .collect()
261}
262
263fn get_distances(
264 panes: &[String],
265 available_colors: &HashMap<String, RGB>,
266 target: RGB,
267) -> Vec<(String, f64)> {
268 let (sender, reciever) = channel();
269 thread::scope(|x| {
270 let mut panes = panes.to_vec();
271 for color in available_colors {
272 let sender_clone = sender.clone();
273 panes.push(color.0.clone());
274 let new_panes = panes.clone();
275 x.spawn(move || {
276 let _ = sender_clone.send((
277 color.0.clone(),
278 calculate_distance(
279 calculate_color_from_panes_custom(&new_panes, available_colors),
280 target,
281 ),
282 ));
283 });
284 panes.pop();
285 }
286 drop(sender);
287 });
288 let mut distance_pairs = Vec::new();
289 for message in reciever {
290 distance_pairs.push(message);
291 }
292 distance_pairs
293}