Skip to main content

src/find_dominant_colors.rs

1use kmeans_colors::get_kmeans_hamerly;
2use palette::{color_difference::Wcag21RelativeContrast, FromColor, Lab, Srgb};
4pub fn find_dominant_colors(lab: &Vec<Lab>, max_colors: usize) -> Vec<Lab> {
5 // This is based on code from the kmeans-colors binary, but with a bunch of
6 // the options stripped out.
7 // See https://github.com/okaneco/kmeans-colors/blob/0.5.0/src/bin/kmeans_colors/app.rs
8 let max_iterations = 20;
9 let converge = 1.0;
10 let verbose = false;
11 let seed: u64 = 0;
13 assert!(lab.len() > 0);
15 let result = get_kmeans_hamerly(max_colors, max_iterations, converge, verbose, lab, seed);
17 result.centroids
20pub fn choose_best_color_for_bg(colors: Vec<Lab>, background: &Srgb<u8>) -> Vec<Lab> {
21 // Start by adding black and white to the list of candidate colors.
22 //
23 // They're boring, but any background colour will always have sufficient
24 // contrast with at least one of them.
25 let black = Srgb::new(0.0, 0.0, 0.0);
26 let white = Srgb::new(1.0, 1.0, 1.0);
28 // I suspect this is not the most "technically correct" way to convert
29 // an Srgb<u8> to a Srgb<f32>, but it's good enough for my purposes.
30 let mut extended_colors: Vec<Srgb<f32>> =
31 colors.iter().map(|c| Srgb::<f32>::from_color(*c)).collect();
33 extended_colors.push(black);
34 extended_colors.push(white);
36 let background: Srgb<f32> = Srgb::new(
37 background.red as f32 / 255.0,
38 background.green as f32 / 255.0,
39 background.blue as f32 / 255.0,
40 );
42 // Filter for colors which meet the min contrast ratio
43 let allowed_colors: Vec<Srgb<f32>> = extended_colors
44 .into_iter()
45 .filter(|c| background.has_min_contrast_graphics(*c))
46 .collect();
48 // Now pick the color with the highest saturation among the remaining.
49 let best_color: Srgb<f32> = allowed_colors
50 .into_iter()
51 .max_by(|color_a, color_b| {
52 saturation(color_a)
53 .partial_cmp(&saturation(color_b))
54 .unwrap()
55 })
56 .unwrap();
58 vec![Lab::from_color(best_color)]
61// Based on https://filmentor.academy/en/blogs/news/die-wunderbare-welt-der-mathematik-fur-farben
62fn saturation(c: &Srgb<f32>) -> f32 {
63 let min_rgb: f32 = vec![c.red, c.green, c.blue]
64 .into_iter()
65 .min_by(|a, b| a.partial_cmp(b).unwrap())
66 .unwrap();
67 let max_rgb: f32 = vec![c.red, c.green, c.blue]
68 .into_iter()
69 .max_by(|a, b| a.partial_cmp(b).unwrap())
70 .unwrap();
72 if min_rgb == max_rgb {
73 return 0.0;
74 }
76 let luminosity: f32 = 0.5 * (min_rgb + max_rgb);
78 if luminosity == 1.0 {
79 0.0
80 } else {
81 (max_rgb - min_rgb) / (1.0 - (2.0 * luminosity - 1.0).abs())
82 }