Skip to main content

material_colors/quantize/
quantizer_wsmeans.rs

1use 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                // Use existing points rather than generating random centroids.
107                //
108                // KMeans is extremely sensitive to initial clusters. This quantizer
109                // is meant to be used with a Wu quantizer that provides initial
110                // centroids, but Wu is very slow on unscaled images and when extracting
111                // more than 256 colors.
112                //
113                // Here, we can safely assume that more than 256 colors were requested
114                // for extraction. Generating random centroids tends to lead to many
115                // "empty" centroids, as the random centroids are nowhere near any pixels
116                // in the image, and the centroids from Wu are very refined and close
117                // to pixels in the image.
118                //
119                // Rather than generate random centroids, we'll pick centroids that
120                // are actual pixels in the image, and avoid duplicating centroids.
121
122                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 WHITE: Argb = Argb::from_u32(0xffffffff);
363    // const RANDOM: Argb = Argb::from_u32(0xff426088);
364    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}