Skip to main content

Implement a basic version of background checking

ID
c523110
date
2024-05-12 12:11:48+00:00
author
Alex Chan <alex@alexwlchan.net>
parent
6834b80
message
Implement a basic version of background checking
changed files
2 files, 53 additions, 13 deletions

Changed files

src/find_dominant_colors.rs (734) → src/find_dominant_colors.rs (2228)

diff --git a/src/find_dominant_colors.rs b/src/find_dominant_colors.rs
index 88ed12f..a437a4c 100644
--- a/src/find_dominant_colors.rs
+++ b/src/find_dominant_colors.rs
@@ -1,7 +1,7 @@
 use kmeans_colors::get_kmeans_hamerly;
-use palette::{FromColor, Lab, Srgb};
+use palette::{color_difference::Wcag21RelativeContrast, FromColor, Hsl, Lab, Srgb};
 
-pub fn find_dominant_colors(lab: &Vec<Lab>, max_colors: usize) -> Vec<Srgb<u8>> {
+pub fn find_dominant_colors(lab: &Vec<Lab>, max_colors: usize) -> Vec<Lab> {
     // This is based on code from the kmeans-colors binary, but with a bunch of
     // the options stripped out.
     // See https://github.com/okaneco/kmeans-colors/blob/0.5.0/src/bin/kmeans_colors/app.rs
@@ -12,11 +12,46 @@ pub fn find_dominant_colors(lab: &Vec<Lab>, max_colors: usize) -> Vec<Srgb<u8>> 
 
     let result = get_kmeans_hamerly(max_colors, max_iterations, converge, verbose, lab, seed);
 
-    let rgb: Vec<Srgb<u8>> = result
-        .centroids
-        .iter()
-        .map(|x| Srgb::from_color(*x).into_format())
-        .collect::<Vec<Srgb<u8>>>();
+    result.centroids
+}
+
+pub fn choose_best_color_for_bg(colors: Vec<Lab>, background: &Srgb<u8>) -> Vec<Lab> {
+    // Start by adding black and white to the list of candidate colors.
+    //
+    // They're boring, but any background colour will always have sufficient
+    // contrast with at least one of them.
+    let black = Srgb::new(0.0, 0.0, 0.0);
+    let white = Srgb::new(1.0, 1.0, 1.0);
+
+    // I suspect this is not the most "technically correct" way to convert
+    // an Srgb<u8> to a Srgb<f32>, but it's good enough for my purposes.
+    let mut extended_colors: Vec<Srgb<f32>> =
+        colors.iter().map(|c| Srgb::<f32>::from_color(*c)).collect();
+
+    extended_colors.push(black);
+    extended_colors.push(white);
+
+    let background: Srgb<f32> = Srgb::new(
+        background.red as f32 / 255.0,
+        background.green as f32 / 255.0,
+        background.blue as f32 / 255.0,
+    );
+
+    // Filter for colors which meet the min contrast ratio
+    let allowed_colors: Vec<Srgb<f32>> = extended_colors
+        .into_iter()
+        .filter(|c| background.has_min_contrast_text(*c))
+        .collect();
+
+    // Now pick the color with the highest saturation among the remaining.
+    let best_color: Srgb<f32> = allowed_colors
+        .into_iter()
+        .max_by(|color_a, color_b| {
+            let saturation_a = Hsl::new_srgb(color_a.red, color_a.green, color_a.blue).saturation;
+            let saturation_b = Hsl::new_srgb(color_b.red, color_b.green, color_b.blue).saturation;
+            saturation_a.partial_cmp(&saturation_b).unwrap()
+        })
+        .unwrap();
 
-    rgb
+    vec![Lab::from_color(best_color)]
 }

src/main.rs (8035) → src/main.rs (8239)

diff --git a/src/main.rs b/src/main.rs
index 0150d74..3323a51 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -3,7 +3,7 @@
 #[macro_use]
 extern crate clap;
 
-use palette::{Lab, Srgb};
+use palette::{FromColor, Lab, Srgb};
 
 mod cli;
 mod find_dominant_colors;
@@ -27,11 +27,16 @@ fn main() {
     let background = matches.get_one::<Srgb<u8>>("BACKGROUND_HEX");
 
     let selected_colors = match background {
-        Some(_bg) => dominant_colors,
-        None      => dominant_colors,
+        Some(bg) => find_dominant_colors::choose_best_color_for_bg(dominant_colors.clone(), bg),
+        None => dominant_colors,
     };
 
-    for c in selected_colors {
+    let rgb_colors = selected_colors
+        .iter()
+        .map(|c| Srgb::from_color(*c).into_format())
+        .collect::<Vec<Srgb<u8>>>();
+
+    for c in rgb_colors {
         print_color(c, &background, matches.get_flag("no-palette"));
     }
 }
@@ -188,7 +193,7 @@ mod tests {
         assert_eq!(output.stdout, "");
         assert_eq!(
             output.stderr,
-            "error: invalid value 'NaN' for '--max-colours <MAX-COLOURS>': invalid digit found in string\n\nFor more information, try '--help'.\n"
+            "error: invalid value 'NaN' for '--max-colours <MAX_COLOURS>': invalid digit found in string\n\nFor more information, try '--help'.\n"
         );
     }