palette_extract/mmcq_impl/
mod.rs1mod config;
2mod histogram;
3mod pixel_encoding;
4mod types;
5mod util;
6mod vbox;
7
8pub use types::Color;
9pub use pixel_encoding::PixelEncoding;
10
11use std::cmp::{self, Ordering};
12
13use histogram::create_histogram_and_vbox;
14use config::{FRACTION_BY_POPULATION, MAX_ITERATIONS, VBOX_LENGTH};
15use util::color_index_from;
16use vbox::VBox;
17use types::ColorChannel;
18
19pub fn extract_colors(
20 pixels: &[u8],
21 encoding: PixelEncoding,
22 quality: u8,
23 max_colors: u8,
24 ignore_white: bool,
25) -> Vec<Color> {
26 let vbox = create_histogram_and_vbox(
27 pixels,
28 encoding,
29 quality,
30 ignore_white,
31 );
32
33 let mut pq = vec![vbox];
35
36 let target = (FRACTION_BY_POPULATION * max_colors as f32).ceil() as u32;
38
39 iterate(&mut pq, sort_by_count, target);
40
41 pq.sort_by(sort_by_product);
42
43 let len_before = pq.len() as u32;
44
45 iterate(&mut pq, sort_by_product, max_colors as u32 - len_before);
46
47 pq.reverse();
48
49 pq.iter().map(|v| v.get_average()).collect()
50}
51
52fn apply_median_cut(vbox: VBox) -> Vec<VBox> {
53 if vbox.get_count() == 0 {
54 return vec![];
55 }
56
57 if vbox.get_count() == 1 {
59 return vec![vbox];
60 }
61
62 let histogram = &vbox.histogram;
63
64 let mut total: u32 = 0;
66 let mut partial_sum: Vec<i32> = vec![-1; VBOX_LENGTH.into()]; let axis = vbox.widest_color_channel();
68
69 match axis {
70 ColorChannel::R => {
71 for r in vbox.r_range() {
72 let mut sum: u32 = 0;
73 for g in vbox.g_range() {
74 for b in vbox.b_range() {
75 let index = color_index_from(r, g, b);
76 sum += histogram[index as usize];
77 }
78 }
79 total += sum;
80 partial_sum[r as usize] = total as i32;
81 }
82 }
83 ColorChannel::G => {
84 for g in vbox.g_range() {
85 let mut sum: u32 = 0;
86 for r in vbox.r_range() {
87 for b in vbox.b_range() {
88 let index = color_index_from(r, g, b);
89 sum += histogram[index as usize];
90 }
91 }
92 total += sum;
93 partial_sum[g as usize] = total as i32;
94 }
95 }
96 ColorChannel::B => {
97 for b in vbox.b_range() {
98 let mut sum: u32 = 0;
99 for r in vbox.r_range() {
100 for g in vbox.g_range() {
101 let index = color_index_from(r, g, b);
102 sum += histogram[index as usize];
103 }
104 }
105 total += sum;
106 partial_sum[b as usize] = total as i32;
107 }
108 }
109 }
110
111 let mut look_ahead_sum: Vec<i32> = vec![-1; VBOX_LENGTH.into()]; for (i, sum) in partial_sum.iter().enumerate().filter(|(_, &sum)| sum != -1) {
113 look_ahead_sum[i] = total as i32 - sum
114 }
115
116 return cut(axis, &vbox, &partial_sum, &look_ahead_sum, total);
117}
118
119fn cut(
120 axis: ColorChannel,
121 vbox: &VBox,
122 partial_sum: &[i32],
123 look_ahead_sum: &[i32],
124 total: u32,
125) -> Vec<VBox> {
126 let vbox_min: i32;
127 let vbox_max: i32;
128
129 match axis {
130 ColorChannel::R => {
131 vbox_min = vbox.get_r_min().into();
132 vbox_max = vbox.get_r_max().into();
133 }
134 ColorChannel::G => {
135 vbox_min = vbox.get_g_min().into();
136 vbox_max = vbox.get_g_max().into();
137 }
138 ColorChannel::B => {
139 vbox_min = vbox.get_b_min().into();
140 vbox_max = vbox.get_b_max().into();
141 }
142 }
143
144 for l in (vbox_min..(vbox_max + 1)).filter(|&i| partial_sum[i as usize] > (total / 2) as i32) {
145 let mut vbox1 = VBox::new_from(&vbox);
146 let mut vbox2 = VBox::new_from(&vbox);
147
148 let left = l - vbox_min;
149 let right = vbox_max - l;
150
151 let mut d2 = if left <= right {
152 cmp::min(vbox_max - 1, l + right / 2)
153 } else {
154 cmp::max(vbox_min, (((l - 1) as f32) - (left as f32 / 2.0)) as i32)
157 };
158
159 while d2 < 0 || partial_sum[d2 as usize] <= 0 {
160 d2 += 1;
161 }
162
163 let mut count2: i32 = look_ahead_sum[d2 as usize];
164 while count2 == 0 && d2 > 0 && partial_sum[(d2 - 1) as usize] > 0 {
165 d2 -= 1;
166 count2 = look_ahead_sum[d2 as usize];
167 }
168
169 vbox1.set_max(d2 as u8, &axis);
170 vbox2.set_min(d2 as u8 + 1, &axis);
171
172 return vec![vbox1, vbox2];
173 }
174
175 panic!("VBox can't be cut")
176}
177
178fn sort_by_count(l: &VBox, r: &VBox) -> Ordering {
179 l.get_count().cmp(&r.get_count())
180}
181
182fn sort_by_product(a: &VBox, b: &VBox) -> Ordering {
183 let a_count = a.get_count();
184 let b_count = b.get_count();
185 let a_volume = a.get_volume();
186 let b_volume = b.get_volume();
187
188 if a_count == b_count {
189 return a_volume.cmp(&b_volume);
191 } else {
192 let a_product = a_count as u64 * a_volume as u64;
194 let b_product = b_count as u64 * b_volume as u64;
195 return a_product.cmp(&b_product);
196 }
197}
198
199fn iterate(queue: &mut Vec<VBox>, comp: fn(&VBox, &VBox) -> Ordering, target: u32) {
200 let mut color = 1;
201
202 for _ in 0..MAX_ITERATIONS {
203 let last_item = match queue.last() {
204 Some(v) => v,
205 None => return,
206 };
207
208 if last_item.get_count() == 0 {
209 queue.sort_by(comp);
210 continue;
211 }
212 let vbox = queue.pop().unwrap();
213 let mut new_boxes = apply_median_cut(vbox);
214 queue.push(new_boxes.remove(0));
215 if new_boxes.len() == 1 {
216 queue.push(new_boxes.remove(0));
217 color += 1
218 }
219 queue.sort_by(comp);
220
221 if color >= target {
222 return;
223 }
224 }
225}