Skip to main content

Resize the image, tighter convergence, fix –no-palette

ID
46aee3b
date
2021-11-26 08:27:56+00:00
author
Alex Chan <alex@alexwlchan.net>
parent
c0b65ea
message
Resize the image, tighter convergence, fix --no-palette
changed files
1 file, 35 additions, 18 deletions

Changed files

src/main.rs (2186) → src/main.rs (3550)

diff --git a/src/main.rs b/src/main.rs
index 0b275da..395e4fe 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -4,6 +4,7 @@
 extern crate clap;
 
 use clap::{App, Arg};
+use image::imageops::FilterType;
 use palette::{Lab, Pixel, Srgb, Srgba};
 use kmeans_colors::{get_kmeans_hamerly};
 
@@ -37,42 +38,58 @@ fn main() {
     // This .unwrap() is safe because "path" is a required param
     let path = matches.value_of("path").unwrap();
 
-    // Get the count as a number, or default to 5.
+    // Get the count as a number.
     // See https://github.com/clap-rs/clap/blob/v2.33.1/examples/12_typed_values.rs
-    let count = value_t!(matches, "count", usize)
-        .unwrap_or_else(|e| e.exit());
+    let count = value_t!(matches, "count", usize).unwrap_or_else(|e| e.exit());
 
-    let img = image::open(&path)
-        .unwrap()
-        .into_rgba8();
+    // Open the image, then resize it.  For this tool I'd rather get a good answer
+    // quickly than a great answer slower.
+    //
+    // The choice of max dimension is arbitrary.  Making it smaller means you get
+    // faster results, but possibly at the loss of quality.
+    //
+    // The nearest neighbour algorithm produces images that don't look as good,
+    // but it's much much faster and the loss of quality is unlikely to be
+    // an issue when looking for dominant colours.
+    //
+    // Note: when trying to work out what's "fast enough", make sure you use release
+    // mode.  The image/k-means operations are significantly faster (=2 orders
+    // of magnitude) than in debug mode.
+    //
+    // See https://docs.rs/image/0.23.14/image/imageops/enum.FilterType.html
+    let img = image::open(&path).unwrap();
+    let resized_img = img.resize(400, 400, FilterType::Nearest);
 
-    let img_vec = img.into_raw();
+    let img_vec = resized_img.into_rgba8().into_raw();
 
+    // 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/9960c55dbc572e08d564dc341d6fd7e66fa79b5e/src/bin/kmeans_colors/app.rs
     let lab: Vec<Lab> = Srgba::from_raw_slice(&img_vec)
         .iter()
         .map(|x| x.into_format().into())
         .collect();
 
     let max_iterations = 20;
-    let converge = 50.0;
+    let converge = 1.0;
     let verbose = false;
     let seed: u64 = 0;
 
-    let run_result = get_kmeans_hamerly(
-        count,
-        max_iterations,
-        converge,
-        verbose,
-        &lab,
-        seed,
-    );
+    let result = get_kmeans_hamerly(count, max_iterations, converge, verbose, &lab, seed);
 
-    let rgb = &run_result.centroids
+    let rgb = &result.centroids
         .iter()
         .map(|x| Srgb::from(*x).into_format())
         .collect::<Vec<Srgb<u8>>>();
 
+    // This uses ANSI escape sequences and Unicode block elements to print
+    // a palette of hex strings which are coloured to match.
+    // See https://alexwlchan.net/2021/04/coloured-squares/
     for c in rgb {
-        println!("\x1B[38;2;{};{};{}m▇ #{:02x}{:02x}{:02x}\x1B[0m", c.red, c.green, c.blue, c.red, c.green, c.blue);
+        if matches.is_present("no-palette") {
+            println!("#{:02x}{:02x}{:02x}", c.red, c.green, c.blue);
+        } else {
+            println!("\x1B[38;2;{};{};{}m▇ #{:02x}{:02x}{:02x}\x1B[0m", c.red, c.green, c.blue, c.red, c.green, c.blue);
+        }
     }
 }