about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorgennyble <gen@nyble.dev>2021-05-22 08:53:09 -0500
committergennyble <gen@nyble.dev>2021-05-22 08:53:09 -0500
commit711a26d0690ec9ca4cc93ecc864b8fe65785e14e (patch)
treeeb1ba9fd086e159f77af16d29700711c927d0d95 /src
parent4d9bab22ff41a7089e58516336005002a934cbd3 (diff)
downloadcolorsquash-711a26d0690ec9ca4cc93ecc864b8fe65785e14e.tar.gz
colorsquash-711a26d0690ec9ca4cc93ecc864b8fe65785e14e.zip
now comparing in hsv
Diffstat (limited to 'src')
-rw-r--r--src/main.rs288
1 files changed, 174 insertions, 114 deletions
diff --git a/src/main.rs b/src/main.rs
index 67ef397..9513a62 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,122 +1,182 @@
 use std::{cmp::Ordering, collections::HashMap, env::args, time::Instant};
 
-use image::{io::Reader as ImageReader};
+use image::io::Reader as ImageReader;
 use image::Rgb;
 
 fn main() {
-	let before = Instant::now();
+    let before = Instant::now();
     let filename = std::env::args().skip(1).next().unwrap();
-	let outname = std::env::args().skip(2).next().unwrap();
-	// The percent of RGB value difference a color has to surpass to be considere unique
-	let tolerance = 0.3;
-	let rgb_tolerance = (256.0 * tolerance) as u16;
-	let max_colors = 256;
-
-	println!("File is {}", &filename);
-
-	let imageread = ImageReader::open(&filename).expect("Failed to open image!");
-	let mut image = imageread.decode().expect("Failed to decode image!").into_rgb8();
-
-	println!("Decoded!");
-	let before_algo = Instant::now();
-
-	let mut colors: HashMap<Rgb<u8>, usize> = HashMap::new();
-
-	for pixel in image.pixels() {
-		match colors.get_mut(pixel) {
-			None => {
-				colors.insert(*pixel, 1);
-			},
-			Some(n) => {
-				*n += 1
-			}
-		}
-	}
-
-	println!("{} has {} colors in it. Sorting most occuring to least...", filename, colors.len());
-
-	let mut sorted: Vec<(Rgb<u8>, usize)> = colors.into_iter().collect();
-	sorted.sort_by(|a, b| {a.1.cmp(&b.1).then(a.0.0.cmp(&b.0.0).reverse())});
-
-	println!("Sorted! Selecting colors...");
-
-	for (color, count) in sorted.iter().take(10) {
-		println!("{:?} count {}", color, count);
-	}
-
-	let mut sorted_iter = sorted.iter();
-
-	let mut selected_colors: Vec<Rgb<u8>> = Vec::with_capacity(max_colors);
-	selected_colors.push(sorted_iter.next().unwrap().0);
-
-	for (key, _value) in sorted_iter {
-		if selected_colors.len() < max_colors {
-			for selected_color in selected_colors.iter() {
-				if rgb_difference(key, selected_color) > rgb_tolerance {
-					selected_colors.push(*key);
-					break;
-				}
-			}
-		} else {
-			break;
-		}
-	}
-
-	for color in selected_colors.iter().take(10) {
-		println!("selected {:?}", color);
-	}
-
-	println!("Selected {} colors! Creating map...", selected_colors.len());
-
-	let mut color_map: HashMap<Rgb<u8>, Rgb<u8>> = HashMap::with_capacity(sorted.len());
-	// Selected colors are themselves
-	for color in selected_colors.iter() {
-		color_map.insert(*color, *color);
-	}
-
-	// Max complexity is O(n * max_colors)
-	'sorted_colors: for (key, _value) in sorted.iter() {
-		let mut min_difference = 769; // One greater than the max difference
-		let mut min_difference_color = *key;
-
-		for index in 0..selected_colors.len() {
-			let difference = rgb_difference(key, unsafe { selected_colors.get_unchecked(index) });
-			if difference == 0 {
-				continue 'sorted_colors;
-			}
-			if difference < min_difference {
-				min_difference = difference;
-				min_difference_color = unsafe {*selected_colors.get_unchecked(index) };
-			}
-		}
-
-		color_map.insert(*key, min_difference_color);
-	}
-
-	println!("Mapped! Filling in image...");
-
-	for pixel in image.pixels_mut() {
-		pixel.clone_from(color_map.get(pixel).unwrap());
-	}
-
-	println!("Filled! Took {}ms. Recounting colors...", Instant::now().duration_since(before_algo).as_millis());
-
-	let mut recounted_colors = Vec::with_capacity(max_colors);
-	// Recount colors
-	for pixel in image.pixels() {
-		if !recounted_colors.contains(pixel) {
-			recounted_colors.push(*pixel);
-		}
-	}
-	
-	println!("Aiming for a max of {} colors, got {}", max_colors, recounted_colors.len());
-
-	image.save(outname).expect("Failed to write out");
-	println!("Took {}ms", Instant::now().duration_since(before).as_millis());
+    let outname = std::env::args().skip(2).next().unwrap();
+    // The percent of RGB value difference a color has to surpass to be considere unique
+    let tolerance = 0.6;
+    let rgb_tolerance = 10.0 * tolerance;
+    let max_colors = 256;
+
+    println!("File is {}", &filename);
+
+    let imageread = ImageReader::open(&filename).expect("Failed to open image!");
+    let mut image = imageread
+        .decode()
+        .expect("Failed to decode image!")
+        .into_rgb8();
+
+    println!("Decoded!");
+    let before_algo = Instant::now();
+
+    let mut colors: HashMap<Rgb<u8>, usize> = HashMap::new();
+
+    for pixel in image.pixels() {
+        match colors.get_mut(pixel) {
+            None => {
+                colors.insert(*pixel, 1);
+            }
+            Some(n) => *n += 1,
+        }
+    }
+
+    println!(
+        "{} has {} colors in it. Sorting most occuring to least...",
+        filename,
+        colors.len()
+    );
+
+    let mut sorted: Vec<(Rgb<u8>, usize)> = colors.into_iter().collect();
+    sorted.sort_by(|a, b| a.1.cmp(&b.1).then(a.0 .0.cmp(&b.0 .0)).reverse());
+
+    println!("Sorted! Selecting colors...");
+
+    for (color, count) in sorted.iter().take(10) {
+        println!("{:?} count {}", color, count);
+    }
+
+    for (color, count) in sorted.iter().rev().take(10) {
+        println!("rev {:?} count {}", color, count);
+    }
+
+    let mut sorted_iter = sorted.iter();
+
+    let mut selected_colors: Vec<Rgb<u8>> = Vec::with_capacity(max_colors);
+    selected_colors.push(sorted_iter.next().unwrap().0);
+
+    for (key, _value) in sorted_iter {
+        if selected_colors.len() < max_colors {
+            for selected_color in selected_colors.iter() {
+                if rgb_difference(key, selected_color) > rgb_tolerance {
+                    selected_colors.push(*key);
+                    break;
+                }
+            }
+        } else {
+            break;
+        }
+    }
+
+    for color in selected_colors.iter().take(10) {
+        println!("selected {:?}", color);
+    }
+
+    println!("Selected {} colors! Creating map...", selected_colors.len());
+
+    let mut color_map: HashMap<Rgb<u8>, Rgb<u8>> = HashMap::with_capacity(sorted.len());
+    // Selected colors are themselves
+    for color in selected_colors.iter() {
+        color_map.insert(*color, *color);
+    }
+
+    // Max complexity is O(n * max_colors)
+    'sorted_colors: for (key, _value) in sorted.iter() {
+        let mut min_difference = f64::MAX;
+        let mut min_difference_color = *key;
+
+        for index in 0..selected_colors.len() {
+            let difference = rgb_difference(key, unsafe { selected_colors.get_unchecked(index) });
+            /*if difference == 0.0 {
+                continue 'sorted_colors;
+            }*/
+            if difference < min_difference {
+                min_difference = difference;
+                min_difference_color = unsafe { *selected_colors.get_unchecked(index) };
+            }
+        }
+
+        color_map.insert(*key, min_difference_color);
+    }
+
+    println!("Mapped! Filling in image...");
+
+    for pixel in image.pixels_mut() {
+        pixel.clone_from(color_map.get(pixel).unwrap());
+    }
+
+    println!(
+        "Filled! Took {}ms. Recounting colors...",
+        Instant::now().duration_since(before_algo).as_millis()
+    );
+
+    let mut recounted_colors = Vec::with_capacity(max_colors);
+    // Recount colors
+    for pixel in image.pixels() {
+        if !recounted_colors.contains(pixel) {
+            println!("Found unique color! Now {}", recounted_colors.len());
+            recounted_colors.push(*pixel);
+        }
+    }
+
+    println!(
+        "Aiming for a max of {} colors, got {}",
+        max_colors,
+        recounted_colors.len()
+    );
+
+    image.save(outname).expect("Failed to write out");
+    println!(
+        "Took {}ms",
+        Instant::now().duration_since(before).as_millis()
+    );
 }
 
-fn rgb_difference(a: &Rgb<u8>, b: &Rgb<u8>) -> u16 {
-	//((a.0[0] as i16 - b.0[0] as i16).abs() + (a.0[1] as i16 - b.0[1] as i16).abs() +(a.0[2] as i16 - b.0[2] as i16).abs()) as u16
-	//(a.0[0] as i16 - b.0[0] as i16).abs().max((a.0[1] as i16 - b.0[1] as i16).abs().max(a.0[2] as i16 - b.0[2] as i16).abs()) as u16
-	(a.0[0] as i16 - b.0[0] as i16).abs().max((a.0[1] as i16 - b.0[1] as i16).abs()).max((a.0[2] as i16 - b.0[2] as i16).abs()) as u16
-}
\ No newline at end of file
+fn rgb_difference(a: &Rgb<u8>, z: &Rgb<u8>) -> f64 {
+    //((a.0[0] as i16 - b.0[0] as i16).abs() + (a.0[1] as i16 - b.0[1] as i16).abs() +(a.0[2] as i16 - b.0[2] as i16).abs()) as u16
+    //(a.0[0] as i16 - b.0[0] as i16).abs().max((a.0[1] as i16 - b.0[1] as i16).abs().max(a.0[2] as i16 - b.0[2] as i16).abs()) as u16
+    //(a.0[0] as i16 - b.0[0] as i16).abs().max((a.0[1] as i16 - b.0[1] as i16).abs()).max((a.0[2] as i16 - b.0[2] as i16).abs()) as u16
+    /*(((a.0[0] as i32 - b.0[0] as i32) * (a.0[0] as i32 - b.0[0] as i32))
+    + ((a.0[1] as i32 - b.0[1] as i32) * (a.0[1] as i32 - b.0[1] as i32))
+    + ((a.0[2] as i32 - b.0[2] as i32) * (a.0[2] as i32 - b.0[2] as i32)))
+    .abs() as u16*/
+    let (a, b, c) = pixel_rgb_to_hsv(a);
+    let (d, e, f) = pixel_rgb_to_hsv(z);
+
+    (((c - f) * (c - f)) + ((a - d).abs() / 90.0) + (b - e).abs()) as f64
+}
+
+fn pixel_rgb_to_hsv(a: &Rgb<u8>) -> (f32, f32, f32) {
+    let (r, g, b) = (
+        a.0[0] as f32 / 256.0,
+        a.0[1] as f32 / 256.0,
+        a.0[2] as f32 / 256.0,
+    );
+
+    let value = r.max(g.max(b));
+    let x_min = r.min(g.min(b));
+    let chroma = value - x_min;
+
+    let hue = if chroma == 0.0 {
+        0.0
+    } else if value == r {
+        60.0 * ((g - b) / chroma)
+    } else if value == g {
+        60.0 * (2.0 + (b - r) / chroma)
+    } else if value == b {
+        60.0 * (4.0 + (r - g) / chroma)
+    } else {
+        unreachable!()
+    };
+
+    let value_saturation = if value == 0.0 { 0.0 } else { chroma / value };
+
+    /* Rotate the color wheel counter clockwise to the negative location
+          |       Keep the wheel in place and remove any full rotations
+     _____V____ _____V____
+    |          |          |*/
+    ((hue + 360.0) % 360.0, value_saturation * 2.0, value * 2.0)
+}