diff options
Diffstat (limited to 'src/selection.rs')
-rw-r--r-- | src/selection.rs | 118 |
1 files changed, 118 insertions, 0 deletions
diff --git a/src/selection.rs b/src/selection.rs new file mode 100644 index 0000000..dacd735 --- /dev/null +++ b/src/selection.rs @@ -0,0 +1,118 @@ +use std::collections::HashMap; + +#[cfg(feature = "kmeans")] +use crate::nih_kmeans::KMeans; +use rgb::{ComponentBytes, RGB8}; + +use crate::{ + difference::{self, DiffFn}, + ImageData, +}; + +pub trait Selector { + // wanted Into<ImageData> here but rustc got mad about vtable building + // because we store this as Box<dyn Selector> in Squasher and it's builder + fn select(&mut self, max_colors: usize, image: ImageData) -> Vec<RGB8>; +} + +pub struct SortSelect { + tolerance: f32, + difference_fn: Box<DiffFn>, +} + +impl Selector for SortSelect { + /// Pick the colors in the palette from a Vec of colors sorted by number + /// of times they occur, high to low. + fn select(&mut self, max_colours: usize, image: ImageData) -> Vec<RGB8> { + let sorted = Self::unique_and_sort(image); + let tolerance = (self.tolerance / 100.0) * 765.0; + let mut selected_colors: Vec<RGB8> = Vec::with_capacity(max_colours); + + for sorted_color in sorted { + if max_colours <= selected_colors.len() { + break; + } else if selected_colors.iter().all(|selected_color| { + (self.difference_fn)(selected_color, &sorted_color) > tolerance + }) { + selected_colors.push(sorted_color); + } + } + + selected_colors + } +} + +impl SortSelect { + /// How different colours have to be to enter the palette. Should be between + /// 0.0 and 100.0, but is unchecked. + pub fn tolerance(mut self, percent: f32) -> Self { + self.tolerance = percent; + self + } + + /// The function to use to compare colours while selecting the palette. + /// + /// see the [difference] module for functions included with the crate and + /// information on implementing your own. + pub fn difference(mut self, diff_fn: &'static DiffFn) -> Self { + self.difference_fn = Box::new(diff_fn); + self + } + + /// Takes an image buffer of RGB data and fill the color map + fn unique_and_sort<'a, Img>(buffer: Img) -> Vec<RGB8> + where + Img: Into<ImageData<'a>>, + { + let ImageData(rgb) = buffer.into(); + let mut colors: HashMap<RGB8, usize> = HashMap::default(); + + //count pixels + for px in rgb { + match colors.get_mut(px) { + None => { + colors.insert(*px, 1); + } + Some(n) => *n += 1, + } + } + + Self::sort(colors) + } + + fn sort(map: HashMap<RGB8, usize>) -> Vec<RGB8> { + let mut sorted: Vec<(RGB8, usize)> = map.into_iter().collect(); + sorted.sort_by(|(colour1, freq1), (colour2, freq2)| { + freq2 + .cmp(freq1) + .then(colour2.r.cmp(&colour1.r)) + .then(colour2.g.cmp(&colour1.g)) + .then(colour2.b.cmp(&colour1.b)) + }); + + sorted.into_iter().map(|(color, _count)| color).collect() + } +} + +impl Default for SortSelect { + fn default() -> Self { + Self { + tolerance: 3.0, + difference_fn: Box::new(difference::rgb), + } + } +} + +#[cfg(feature = "kmeans")] +#[derive(Debug, Default)] +pub struct Kmeans; + +#[cfg(feature = "kmeans")] +impl Selector for Kmeans { + fn select(&mut self, max_colors: usize, image: ImageData) -> Vec<RGB8> { + let ImageData(rgb) = image; + + let kmean = KMeans::new(rgb.to_vec()); + kmean.get_k_colors(max_colors, max_iter) + } +} |