diff options
-rw-r--r-- | Cargo.lock | 417 | ||||
-rw-r--r-- | Cargo.toml | 16 | ||||
-rw-r--r-- | src/lib.rs | 233 | ||||
-rw-r--r-- | src/main.rs | 175 |
4 files changed, 256 insertions, 585 deletions
diff --git a/Cargo.lock b/Cargo.lock index 0c011f8..76ad636 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3,447 +3,60 @@ version = 3 [[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - -[[package]] -name = "adler32" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" - -[[package]] name = "ahash" -version = "0.7.4" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43bb833f0bf979d8475d38fbf09ed3b8a55e1885fe93ad3f93239fc6a4f17b98" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" dependencies = [ - "getrandom 0.2.3", + "getrandom", "once_cell", "version_check", ] [[package]] -name = "autocfg" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bytemuck" -version = "1.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72957246c41db82b8ef88a5486143830adeb8227ef9837740bdec67724cf2c5b" - -[[package]] -name = "byteorder" -version = "1.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" - -[[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] -name = "color_quant" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" - -[[package]] -name = "colors" +name = "colorsquash" version = "0.1.0" dependencies = [ "ahash", - "image", - "kmeans_colors", - "libc", - "rayon", -] - -[[package]] -name = "crc32fast" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "crossbeam-channel" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4" -dependencies = [ - "cfg-if", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-deque" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e" -dependencies = [ - "cfg-if", - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec02e091aa634e2c3ada4a392989e7c3116673ef0ac5b72232439094d73b7fd" -dependencies = [ - "cfg-if", - "crossbeam-utils", - "lazy_static", - "memoffset", - "scopeguard", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db" -dependencies = [ - "cfg-if", - "lazy_static", ] [[package]] -name = "deflate" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73770f8e1fe7d64df17ca66ad28994a0a623ea497fa69486e14984e715c5d174" -dependencies = [ - "adler32", - "byteorder", -] - -[[package]] -name = "either" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" - -[[package]] name = "getrandom" -version = "0.1.16" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" dependencies = [ "cfg-if", "libc", - "wasi 0.9.0+wasi-snapshot-preview1", + "wasi", ] [[package]] -name = "getrandom" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" -dependencies = [ - "cfg-if", - "libc", - "wasi 0.10.2+wasi-snapshot-preview1", -] - -[[package]] -name = "gif" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a668f699973d0f573d15749b7002a9ac9e1f9c6b220e7b165601334c173d8de" -dependencies = [ - "color_quant", - "weezl", -] - -[[package]] -name = "hermit-abi" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - -[[package]] -name = "image" -version = "0.23.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24ffcb7e7244a9bf19d35bf2883b9c080c4ced3c07a9895572178cdb8f13f6a1" -dependencies = [ - "bytemuck", - "byteorder", - "color_quant", - "gif", - "jpeg-decoder", - "num-iter", - "num-rational", - "num-traits", - "png", - "scoped_threadpool", - "tiff", -] - -[[package]] -name = "jpeg-decoder" -version = "0.1.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "229d53d58899083193af11e15917b5640cd40b29ff475a1fe4ef725deb02d0f2" -dependencies = [ - "rayon", -] - -[[package]] -name = "kmeans_colors" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85d1507a3f5aeb9a05fe973e13a41bd46ffbf4f168185eb39f0fcf3ed4cad67e" -dependencies = [ - "rand", - "rand_chacha", -] - -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - -[[package]] name = "libc" -version = "0.2.103" +version = "0.2.138" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8f7255a17a627354f321ef0055d63b898c6fb27eff628af4d1b66b7331edf6" - -[[package]] -name = "memoffset" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59accc507f1338036a0477ef61afdae33cde60840f4dfe481319ce3ad116ddf9" -dependencies = [ - "autocfg", -] - -[[package]] -name = "miniz_oxide" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435" -dependencies = [ - "adler32", -] - -[[package]] -name = "miniz_oxide" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" -dependencies = [ - "adler", - "autocfg", -] - -[[package]] -name = "num-integer" -version = "0.1.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" -dependencies = [ - "autocfg", - "num-traits", -] - -[[package]] -name = "num-iter" -version = "0.1.42" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-rational" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" -dependencies = [ - "autocfg", -] - -[[package]] -name = "num_cpus" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" -dependencies = [ - "hermit-abi", - "libc", -] +checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8" [[package]] name = "once_cell" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" - -[[package]] -name = "png" -version = "0.16.8" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c3287920cb847dee3de33d301c463fba14dda99db24214ddf93f83d3021f4c6" -dependencies = [ - "bitflags", - "crc32fast", - "deflate", - "miniz_oxide 0.3.7", -] - -[[package]] -name = "ppv-lite86" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" - -[[package]] -name = "rand" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" -dependencies = [ - "getrandom 0.1.16", - "libc", - "rand_chacha", - "rand_core", - "rand_hc", -] - -[[package]] -name = "rand_chacha" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" -dependencies = [ - "ppv-lite86", - "rand_core", -] - -[[package]] -name = "rand_core" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" -dependencies = [ - "getrandom 0.1.16", -] - -[[package]] -name = "rand_hc" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" -dependencies = [ - "rand_core", -] - -[[package]] -name = "rayon" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06aca804d41dbc8ba42dfd964f0d01334eceb64314b9ecf7c5fad5188a06d90" -dependencies = [ - "autocfg", - "crossbeam-deque", - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d78120e2c850279833f1dd3582f730c4ab53ed95aeaaaa862a2a5c71b1656d8e" -dependencies = [ - "crossbeam-channel", - "crossbeam-deque", - "crossbeam-utils", - "lazy_static", - "num_cpus", -] - -[[package]] -name = "scoped_threadpool" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8" - -[[package]] -name = "scopeguard" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" - -[[package]] -name = "tiff" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a53f4706d65497df0c4349241deddf35f84cee19c87ed86ea8ca590f4464437" -dependencies = [ - "jpeg-decoder", - "miniz_oxide 0.4.4", - "weezl", -] +checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" [[package]] name = "version_check" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" - -[[package]] -name = "wasi" -version = "0.9.0+wasi-snapshot-preview1" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "wasi" -version = "0.10.2+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" - -[[package]] -name = "weezl" -version = "0.1.5" +version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b77fdfd5a253be4ab714e4ffa3c49caf146b4de743e97510c0656cf90f1e8e" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" diff --git a/Cargo.toml b/Cargo.toml index 9a2c9ad..e1a36ba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,20 +1,20 @@ [package] -name = "colors" +name = "colorsquash" version = "0.1.0" -authors = ["Brad Alfirevic <brad@genbyte.dev>"] +authors = ["Genny Z <gen@nyble.dev>"] edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -image = "0.23.14" +#image = "0.23.14" ahash = "0.7.4" -libc = "0.2.103" -rayon = "*" +#libc = "0.2.103" +#rayon = "*" -[dependencies.kmeans_colors] -version = "0.3" -default-features = false +#[dependencies.kmeans_colors] +#version = "0.3" +#default-features = false [profile.release] debug = true \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..8efd6ad --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,233 @@ +use std::collections::HashMap; +use std::ops::Deref; + +use ahash::RandomState; + +pub struct Squasher<T> { + palette: Vec<(Rgb, usize)>, + larget_count: usize, + map: Vec<T>, +} + +impl<T: Count> Squasher<T> { + /// Creates a new squasher and allocates a new color map. A color map + /// contains every 24-bit color and ends up with an amount of memory + /// equal to `16MB * std::mem::size_of(T)` + pub fn new(max_colors: T, buffer: &[u8]) -> Self { + let sorted = Self::unique_and_sort(buffer); + let selected = Self::select_colors(&sorted, max_colors); + + let mut this = Self { + palette: selected, + larget_count: sorted.first().unwrap().1, + map: vec![T::zero(); 256 * 256 * 256], + }; + + this.map_selected(&sorted); + + this + } + + /// Take an RGB image buffer and an output buffer. The function will fill + /// the output buffer with indexes into the Palette. + pub fn map_image(&mut self, image: &[u8], buffer: &mut [T]) { + let sorted = Self::unique_and_sort(image); + self.map_selected(&sorted); + + for (idx, color) in image.chunks(3).enumerate() { + let index = self.map[color_index(&Rgb([color[0], color[1], color[2]]))]; + + buffer[idx] = index; + } + } + + /// Retrieve the palette this squasher is working from + pub fn palette(&self) -> Vec<Rgb> { + self.palette.iter().map(|ahh| ahh.0).collect() + } + + /// Retrieve the palette as bytes + pub fn palette_bytes(&self) -> Vec<u8> { + self.palette + .clone() + .into_iter() + .map(|rgb| rgb.0.into_iter()) + .flatten() + .collect() + } + + /// Takes an image buffer of RGB data and fill the color map + fn unique_and_sort(buffer: &[u8]) -> Vec<(Rgb, usize)> { + let mut colors: HashMap<Rgb, usize, RandomState> = HashMap::default(); + + //count pixels + for pixel in buffer.chunks(3) { + let rgb = Rgb([pixel[0], pixel[1], pixel[2]]); + + match colors.get_mut(&rgb) { + None => { + colors.insert(rgb, 1); + } + Some(n) => *n += 1, + } + } + + let mut sorted: Vec<(Rgb, usize)> = colors.into_iter().collect(); + sorted.sort_by(|(colour1, freq1), (colour2, freq2)| { + freq2 + .cmp(freq1) + .then(colour2[0].cmp(&colour1[0])) + .then(colour2[1].cmp(&colour1[1])) + .then(colour2[2].cmp(&colour1[2])) + }); + + sorted + } + + fn select_colors(sorted: &[(Rgb, usize)], max_colors: T) -> Vec<(Rgb, usize)> { + #[allow(non_snake_case)] + let RGB_TOLERANCE: f32 = 0.04 * 256.0; + let mut selected_colors: Vec<(Rgb, usize)> = Vec::with_capacity(max_colors.as_usize()); + + for (key, count) in sorted.iter() { + if max_colors.le(&selected_colors.len()) { + break; + } else if selected_colors + .iter() + .all(|color| rgb_difference(key, &color.0) > RGB_TOLERANCE) + { + selected_colors.push((*key, *count)); + } + } + + selected_colors + } + + fn map_selected(&mut self, sorted: &[(Rgb, usize)]) { + for (sorted, _) in sorted { + let mut min_diff = f32::MAX; + let mut min_index = usize::MAX; + + for (index, (selected, count)) in self.palette.iter().enumerate() { + //let count_weight = *count as f32 / self.larget_count as f32; + let diff = rgb_difference(sorted, selected); // - count_weight * 64.0; + + // This is kind of racist genny + /*if selected[0] + selected[1] + selected[2] < 72 { + continue; + }*/ + + //println!("{diff} - {selected:?}"); + + if diff.max(0.0) < min_diff { + min_diff = diff; + min_index = index; + } + } + + self.map[color_index(sorted)] = T::from_usize(min_index); + } + } +} + +pub trait Count: Copy + Clone { + fn zero() -> Self; + fn as_usize(&self) -> usize; + fn from_usize(from: usize) -> Self; + fn le(&self, rhs: &usize) -> bool; +} + +macro_rules! count_impl { + ($kind:ty) => { + impl Count for $kind { + fn zero() -> Self { + 0 + } + + fn as_usize(&self) -> usize { + *self as usize + } + + #[inline(always)] + fn from_usize(from: usize) -> Self { + from as Self + } + + #[inline(always)] + fn le(&self, rhs: &usize) -> bool { + *self as usize <= *rhs + } + } + }; +} + +count_impl!(u8); +count_impl!(u16); +count_impl!(u32); +count_impl!(u64); +count_impl!(usize); + +#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)] +pub struct Rgb([u8; 3]); + +impl Deref for Rgb { + type Target = [u8; 3]; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +#[inline(always)] +fn color_index(c: &Rgb) -> usize { + c.0[0] as usize * (256 * 256) + c.0[1] as usize * 256 + c.0[2] as usize +} + +#[allow(clippy::many_single_char_names)] +#[inline(always)] +fn rgb_difference(a: &Rgb, b: &Rgb) -> f32 { + let absdiff = |a: u8, b: u8| (a as f32 - b as f32).abs(); + + /*let hsv1 = pixel_rgb_to_hsv(a); + let hsv2 = pixel_rgb_to_hsv(b);*/ + + //let diff_max = 3.0; + + absdiff(a.0[0], b.0[0]) + absdiff(a.0[1], b.0[1]) + absdiff(a.0[2], b.0[2]) + /*(((hsv1.0 / 90.0) - (hsv2.0 / 90.0)).abs() + + (hsv1.1 - hsv2.1).abs() + + ((hsv1.2 - hsv1.2).abs())) + / diff_max*/ +} + +fn pixel_rgb_to_hsv(a: &Rgb) -> (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) +} diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index f1915f8..0000000 --- a/src/main.rs +++ /dev/null @@ -1,175 +0,0 @@ -use std::time::Instant; -use std::{collections::HashMap, env::args}; - -use image::io::Reader as ImageReader; -use image::Rgb; - -use ahash::RandomState; - -use rayon::prelude::*; - -const MAX_COLORS: usize = 16; - -const RGB_TOLERANCE: f32 = 0.25 + (1.0 - (MAX_COLORS as f32 / 256.0)); - -fn main() { - let filename = args().nth(1).unwrap(); - let outname = args().nth(2).unwrap(); - // The percent of RGB value difference a color has to surpass to be considered unique - - let imageread = ImageReader::open(&filename).expect("Failed to open image!"); - let mut image = imageread - .decode() - .expect("Failed to decode image!") - .into_rgb8(); - - //let mem_before_sort = mallinfo().hblkhd as usize; - let start_sort = Instant::now(); - let sorted_colors = unique_and_sort(image.pixels()); - println!("Sort took {}s", start_sort.elapsed().as_secs_f32()); - - //let mem_before_selection = mallinfo().hblkhd as usize; - let start_selection = Instant::now(); - let selected_colors = select_colors(&sorted_colors); - println!( - "Color Selection took {}s. Count {}", - start_selection.elapsed().as_secs_f32(), - selected_colors.len() - ); - - let start_array = Instant::now(); - let mut array = vec![0usize; 256 * 256 * 256]; - println!( - "Array creation took {}s", - start_array.elapsed().as_secs_f32() - ); - - let start_map = Instant::now(); - - for (sorted, _) in &sorted_colors { - let mut min_diff = f32::MAX; - let mut min_index = usize::MAX; - - for (index, selected) in selected_colors.iter().enumerate() { - let diff = rgb_difference(sorted, selected); - if diff < min_diff { - min_diff = diff; - min_index = index; - } - } - - array[color_index(sorted)] = min_index; - } - - println!( - "Creating color map {:.2}s", - start_map.elapsed().as_secs_f32() - ); - - let start_fill = Instant::now(); - // Max complexity is O(n * max_colors) - for color in image.pixels_mut() { - let index = array[color_index(color)]; - - *color = selected_colors[index]; - } - println!( - "Took {:.2}s to fill in the image.\nTotal time from sort {:.2}s", - start_fill.elapsed().as_secs_f32(), - start_sort.elapsed().as_secs_f32() - ); - - image.save(outname).expect("Failed to write out"); -} - -fn unique_and_sort<'a, T>(pixels: T) -> Vec<(Rgb<u8>, usize)> -where - T: Iterator<Item = &'a Rgb<u8>>, -{ - let mut colors: HashMap<Rgb<u8>, usize, RandomState> = HashMap::default(); - - //count pixels - for pixel in pixels { - match colors.get_mut(pixel) { - None => { - colors.insert(*pixel, 1); - } - Some(n) => *n += 1, - } - } - - let mut sorted: Vec<(Rgb<u8>, usize)> = colors.into_par_iter().collect(); - sorted.sort_by(|(colour1, freq1), (colour2, freq2)| { - freq2 - .cmp(freq1) - .then(colour2[0].cmp(&colour1[0])) - .then(colour2[1].cmp(&colour1[1])) - .then(colour2[2].cmp(&colour1[2])) - }); - - sorted -} - -fn select_colors(sorted: &[(Rgb<u8>, usize)]) -> Vec<Rgb<u8>> { - let mut selected_colors: Vec<Rgb<u8>> = Vec::with_capacity(MAX_COLORS); - - for (key, _value) in sorted.iter() { - if selected_colors.len() >= MAX_COLORS { - break; - } else if selected_colors - .iter() - .all(|color| rgb_difference(key, color) > RGB_TOLERANCE) - { - selected_colors.push(*key); - } - } - - selected_colors -} - -#[inline(always)] -fn color_index(c: &Rgb<u8>) -> usize { - c.0[0] as usize * (256 * 256) + c.0[1] as usize * 256 + c.0[2] as usize -} - -#[allow(clippy::many_single_char_names)] -#[inline(always)] -fn rgb_difference(a: &Rgb<u8>, z: &Rgb<u8>) -> f32 { - 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 f32 -} - -#[allow(clippy::float_cmp)] -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) -} |