material_colors/quantize/
quantizer_wsmeans.rs1use super::{PointProvider, PointProviderLab, QuantizerResult};
2use crate::{
3 color::{Argb, Lab},
4 utils::random::Random,
5 IndexMap,
6};
7#[cfg(not(feature = "std"))]
8use alloc::{vec, vec::Vec};
9use core::cmp::Ordering;
10#[cfg(feature = "std")]
11use std::{
12 format,
13 string::String,
14 time::Instant,
15 {vec, vec::Vec},
16};
17
18struct DistanceAndIndex {
19 distance: f64,
20 index: usize,
21}
22
23impl DistanceAndIndex {
24 pub const fn new(distance: f64, index: usize) -> Self {
25 Self { distance, index }
26 }
27}
28
29impl Eq for DistanceAndIndex {}
30impl PartialEq for DistanceAndIndex {
31 fn eq(&self, other: &Self) -> bool {
32 self.distance != other.distance
33 }
34}
35
36impl Ord for DistanceAndIndex {
37 fn cmp(&self, other: &Self) -> Ordering {
38 if self.distance < other.distance {
39 Ordering::Less
40 } else if self.distance > other.distance {
41 Ordering::Greater
42 } else {
43 Ordering::Equal
44 }
45 }
46}
47
48impl PartialOrd for DistanceAndIndex {
49 fn lt(&self, other: &Self) -> bool {
50 self.distance < other.distance
51 }
52
53 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
54 Some(self.cmp(other))
55 }
56}
57
58pub struct QuantizerWsmeans;
59
60impl QuantizerWsmeans {
61 const DEBUG: bool = false;
62
63 #[cfg(feature = "std")]
64 fn debug_log<T: Into<String>>(log: T) {
65 if Self::DEBUG {
66 let log: String = log.into();
67
68 std::println!("{log}");
69 }
70 }
71
72 pub fn quantize(
73 input_pixels: &[Argb],
74 max_colors: usize,
75 starting_clusters: &[Argb],
76 ) -> QuantizerResult {
77 let mut pixel_to_count: IndexMap<Argb, u32> = IndexMap::default();
78 let mut points: Vec<Lab> = vec![];
79 let mut pixels: Vec<Argb> = vec![];
80
81 for input_pixel in input_pixels {
82 let pixel_count = pixel_to_count.get_mut(input_pixel);
83
84 if let Some(pixel_count) = pixel_count {
85 *pixel_count += 1;
86 } else {
87 pixels.push(*input_pixel);
88 points.push(PointProviderLab::lab_from_int(input_pixel));
89 pixel_to_count.insert(*input_pixel, 1);
90 }
91 }
92
93 let cluster_count = max_colors.min(points.len());
94
95 let mut clusters = starting_clusters
96 .iter()
97 .map(PointProviderLab::lab_from_int)
98 .collect::<Vec<_>>();
99 let additional_clusters_needed = cluster_count - clusters.len();
100
101 if additional_clusters_needed > 0 {
102 let mut seed_generator = Random::new(0x42688);
103 let mut indices = vec![];
104
105 for _ in 0..additional_clusters_needed {
106 let mut index = seed_generator.next_range(points.len() as i32) as usize;
123
124 while indices.contains(&index) {
125 index = seed_generator.next_range(points.len() as i32) as usize;
126 }
127
128 indices.push(index);
129 }
130
131 for index in indices {
132 clusters.push(points[index]);
133 }
134 }
135
136 #[cfg(feature = "std")]
137 Self::debug_log(format!(
138 "have {} starting clusters, {} points",
139 clusters.len(),
140 points.len()
141 ));
142
143 let mut cluster_indices = fill_array(points.len(), |index| index % cluster_count);
144 let mut index_matrix = vec![vec![0; cluster_count]; cluster_count];
145
146 let mut distance_to_index_matrix: Vec<Vec<DistanceAndIndex>> =
147 fill_array(cluster_count, |_| {
148 fill_array(cluster_count, |index| DistanceAndIndex::new(0.0, index))
149 });
150 let mut pixel_count_sums = vec![0; cluster_count];
151
152 for iteration in 0..10 {
153 if Self::DEBUG {
154 for i in pixel_count_sums.iter_mut().take(cluster_count) {
155 *i = 0;
156 }
157
158 for i in 0..points.len() {
159 let cluster_index = cluster_indices[i];
160 let count = pixel_to_count[&pixels[i]];
161
162 pixel_count_sums[cluster_index] += count;
163 }
164
165 #[cfg(feature = "std")]
166 {
167 let empty_clusters = pixel_count_sums
168 .iter()
169 .take(cluster_count)
170 .filter(|pixel_count_sum| pixel_count_sum == &&0)
171 .count();
172
173 Self::debug_log(format!(
174 "starting iteration {}; {empty_clusters} clusters are empty of {cluster_count}",
175 iteration + 1
176 ));
177 }
178 }
179
180 let mut points_moved = 0;
181
182 for i in 0..cluster_count {
183 for j in (i + 1)..cluster_count {
184 let distance = PointProviderLab::distance(&clusters[i], &clusters[j]);
185
186 distance_to_index_matrix[j][i].distance = distance;
187 distance_to_index_matrix[j][i].index = i;
188 distance_to_index_matrix[i][j].distance = distance;
189 distance_to_index_matrix[i][j].index = j;
190 }
191
192 distance_to_index_matrix[i].sort();
193
194 for j in 0..cluster_count {
195 index_matrix[i][j] = distance_to_index_matrix[i][j].index;
196 }
197 }
198
199 for i in 0..points.len() {
200 let point = points[i];
201 let previous_cluster_index = cluster_indices[i];
202 let previous_cluster = clusters[previous_cluster_index];
203 let previous_distance = PointProviderLab::distance(&point, &previous_cluster);
204
205 let mut minimum_distance = previous_distance;
206 let mut new_cluster_index = None;
207
208 for (j, cluster) in clusters.iter().enumerate().take(cluster_count) {
209 if distance_to_index_matrix[previous_cluster_index][j].distance
210 >= 4.0 * previous_distance
211 {
212 continue;
213 }
214
215 let distance = PointProviderLab::distance(&point, cluster);
216
217 if distance < minimum_distance {
218 minimum_distance = distance;
219 new_cluster_index = Some(j);
220 }
221 }
222
223 if let Some(new_cluster_index) = new_cluster_index {
224 points_moved += 1;
225 cluster_indices[i] = new_cluster_index;
226 }
227 }
228
229 if points_moved == 0 && iteration > 0 {
230 #[cfg(feature = "std")]
231 Self::debug_log(format!("terminated after {iteration} k-means iterations"));
232
233 break;
234 }
235
236 #[cfg(feature = "std")]
237 Self::debug_log(format!("iteration {} moved {points_moved}", iteration + 1));
238
239 let mut component_asums: Vec<f64> = vec![0.0; cluster_count];
240 let mut component_bsums: Vec<f64> = vec![0.0; cluster_count];
241 let mut component_csums: Vec<f64> = vec![0.0; cluster_count];
242
243 for pixel_count_sum in pixel_count_sums.iter_mut().take(cluster_count) {
244 *pixel_count_sum = 0;
245 }
246
247 for i in 0..points.len() {
248 let cluster_index = cluster_indices[i];
249 let point = points[i];
250 let count = pixel_to_count[&pixels[i]];
251
252 pixel_count_sums[cluster_index] += count;
253 component_asums[cluster_index] += point.l * f64::from(count);
254 component_bsums[cluster_index] += point.a * f64::from(count);
255 component_csums[cluster_index] += point.b * f64::from(count);
256 }
257
258 for i in 0..cluster_count {
259 let count = pixel_count_sums[i];
260
261 if count == 0 {
262 clusters[i] = Lab::new(0.0, 0.0, 0.0);
263
264 continue;
265 }
266
267 let a = component_asums[i] / f64::from(count);
268 let b = component_bsums[i] / f64::from(count);
269 let c = component_csums[i] / f64::from(count);
270
271 clusters[i] = Lab::new(a, b, c);
272 }
273 }
274
275 let mut cluster_argbs = vec![];
276 let mut cluster_populations = vec![];
277
278 for i in 0..cluster_count {
279 let count = pixel_count_sums[i];
280
281 if count == 0 {
282 continue;
283 }
284
285 let possible_new_cluster = PointProviderLab::lab_to_int(&clusters[i]);
286
287 if cluster_argbs.contains(&possible_new_cluster) {
288 continue;
289 }
290
291 cluster_argbs.push(possible_new_cluster);
292
293 cluster_populations.push(count);
294 }
295
296 #[cfg(feature = "std")]
297 Self::debug_log(format!(
298 "kmeans finished and generated {} clusters; {cluster_count} were requested",
299 cluster_argbs.len()
300 ));
301
302 let mut input_pixel_to_cluster_pixel: IndexMap<Argb, Argb> = IndexMap::default();
303
304 #[cfg(feature = "std")]
305 let start_time = Instant::now();
306
307 for i in 0..pixels.len() {
308 let input_pixel = pixels[i];
309 let cluster_index = cluster_indices[i];
310 let cluster = clusters[cluster_index];
311 let cluster_pixel = PointProviderLab::lab_to_int(&cluster);
312
313 input_pixel_to_cluster_pixel.insert(input_pixel, cluster_pixel);
314 }
315
316 #[cfg(feature = "std")]
317 let time_elapsed = start_time.elapsed().as_millis();
318
319 #[cfg(feature = "std")]
320 Self::debug_log(format!(
321 "took {time_elapsed} ms to create input to cluster map"
322 ));
323
324 let mut color_to_count: IndexMap<Argb, u32> = IndexMap::default();
325
326 for i in 0..cluster_argbs.len() {
327 let key = cluster_argbs[i];
328 let value = cluster_populations[i];
329
330 color_to_count.insert(key, value);
331 }
332
333 QuantizerResult {
334 color_to_count,
335 input_pixel_to_cluster_pixel,
336 }
337 }
338}
339
340fn fill_array<T>(count: usize, callback: impl Fn(usize) -> T) -> Vec<T> {
341 let mut results: Vec<T> = vec![];
342
343 for index in 0..count {
344 results.push(callback(index));
345 }
346
347 results
348}
349
350#[cfg(test)]
351mod tests {
352 use super::QuantizerWsmeans;
353 use crate::color::Argb;
354 #[cfg(not(feature = "std"))]
355 use alloc::vec::Vec;
356 #[cfg(feature = "std")]
357 use std::vec::Vec;
358
359 const RED: Argb = Argb::from_u32(0xffff0000);
360 const GREEN: Argb = Argb::from_u32(0xff00ff00);
361 const BLUE: Argb = Argb::from_u32(0xff0000ff);
362 const MAX_COLORS: usize = 256;
365
366 #[test]
367 fn test_1rando() {
368 let result = QuantizerWsmeans::quantize(&[Argb::from_u32(0xff141216)], MAX_COLORS, &[]);
369 let colors = result.color_to_count.keys().collect::<Vec<_>>();
370
371 assert_eq!(colors[0], &Argb::from_u32(0xff141216));
372 }
373
374 #[test]
375 fn test_1r() {
376 let result = QuantizerWsmeans::quantize(&[RED], MAX_COLORS, &[]);
377 let colors = result.color_to_count.keys().collect::<Vec<_>>();
378
379 assert_eq!(colors.len(), 1);
380 assert_eq!(colors[0], &RED);
381 }
382
383 #[test]
384 fn test_1g() {
385 let result = QuantizerWsmeans::quantize(&[GREEN], MAX_COLORS, &[]);
386 let colors = result.color_to_count.keys().collect::<Vec<_>>();
387
388 assert_eq!(colors.len(), 1);
389 assert_eq!(colors[0], &GREEN);
390 }
391
392 #[test]
393 fn test_1b() {
394 let result = QuantizerWsmeans::quantize(&[BLUE], MAX_COLORS, &[]);
395 let colors = result.color_to_count.keys().collect::<Vec<_>>();
396
397 assert_eq!(colors.len(), 1);
398 assert_eq!(colors[0], &BLUE);
399 }
400
401 #[test]
402 fn test_5b() {
403 let result = QuantizerWsmeans::quantize(&[BLUE, BLUE, BLUE, BLUE, BLUE], MAX_COLORS, &[]);
404 let colors = result.color_to_count.keys().collect::<Vec<_>>();
405
406 assert_eq!(colors.len(), 1);
407 assert_eq!(colors[0], &BLUE);
408 }
409}