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"
);
}