1#![forbid(unsafe_code)]
14#![warn(missing_docs)]
15
16extern crate rgb;
17
18use std::cmp;
19use std::fmt;
20use std::error;
21use std::u8;
22
23pub use rgb::RGB8 as Color;
24
25const SIGNAL_BITS: i32 = 5; const RIGHT_SHIFT: i32 = 8 - SIGNAL_BITS;
27const MULTIPLIER: i32 = 1 << RIGHT_SHIFT;
28const MULTIPLIER_64: f64 = MULTIPLIER as f64;
29const HISTOGRAM_SIZE: usize = 1 << (3 * SIGNAL_BITS);
30const VBOX_LENGTH: usize = 1 << SIGNAL_BITS;
31const FRACTION_BY_POPULATION: f64 = 0.75;
32const MAX_ITERATIONS: i32 = 1000;
33
34#[allow(missing_docs)]
36#[derive(Clone,Copy,PartialEq,Debug)]
37pub enum ColorFormat {
38 Rgb,
39 Rgba,
40 Argb,
41 Bgr,
42 Bgra,
43}
44
45#[allow(missing_docs)]
47#[derive(Clone,Copy,PartialEq,Debug)]
48pub enum Error {
49 InvalidVBox,
50 VBoxCutFailed,
51}
52
53impl fmt::Display for Error {
54 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
55 let msg = match *self {
56 Error::InvalidVBox => "an invalid VBox",
57 Error::VBoxCutFailed => "failed to cut a VBox",
58 };
59
60 write!(f, "{}", msg)
61 }
62}
63
64impl error::Error for Error {}
65
66pub fn get_palette(
82 pixels: &[u8],
83 color_format: ColorFormat,
84 quality: u8,
85 max_colors: u8,
86) -> Result<Vec<Color>, Error> {
87 assert!(quality > 0 && quality <= 10);
88 assert!(max_colors > 1);
89
90 quantize(&pixels, color_format, quality, max_colors)
91}
92
93enum ColorChannel {
94 Red,
95 Green,
96 Blue,
97}
98
99#[derive(Clone)]
100struct VBox {
101 r_min: u8,
102 r_max: u8,
103 g_min: u8,
104 g_max: u8,
105 b_min: u8,
106 b_max: u8,
107 average: Color,
108 volume: i32,
109 count: i32,
110}
111
112impl VBox {
113 fn new(
114 r_min: u8, r_max: u8,
115 g_min: u8, g_max: u8,
116 b_min: u8, b_max: u8,
117 ) -> VBox {
118 VBox {
119 r_min: r_min,
120 r_max: r_max,
121 g_min: g_min,
122 g_max: g_max,
123 b_min: b_min,
124 b_max: b_max,
125 average: Color::new(0, 0, 0),
126 volume: 0,
127 count: 0,
128 }
129
130 }
132
133 fn recalc(&mut self, histogram: &[i32]) {
134 self.average = self.calc_average(histogram);
135 self.count = self.calc_count(histogram);
136 self.volume = self.calc_volume();
137 }
138
139 fn calc_volume(&self) -> i32 {
141 (self.r_max as i32 - self.r_min as i32 + 1)
142 * (self.g_max as i32 - self.g_min as i32 + 1)
143 * (self.b_max as i32 - self.b_min as i32 + 1)
144 }
145
146 fn calc_count(&self, histogram: &[i32]) -> i32 {
148 let mut count = 0;
149 for i in self.r_min..(self.r_max + 1) {
150 for j in self.g_min..(self.g_max + 1) {
151 for k in self.b_min..(self.b_max + 1) {
152 let index = make_color_index_of(i, j, k);
153 count += histogram[index];
154 }
155 }
156 }
157
158 count
159 }
160
161 fn calc_average(&self, histogram: &[i32]) -> Color {
162 let mut ntot = 0;
163
164 let mut r_sum = 0;
165 let mut g_sum = 0;
166 let mut b_sum = 0;
167
168 for i in self.r_min..(self.r_max + 1) {
169 for j in self.g_min..(self.g_max + 1) {
170 for k in self.b_min..(self.b_max + 1) {
171 let index = make_color_index_of(i, j, k);
172 let hval = histogram[index] as f64;
173 ntot += hval as i32;
174 r_sum += (hval * (i as f64 + 0.5) * MULTIPLIER_64) as i32;
175 g_sum += (hval * (j as f64 + 0.5) * MULTIPLIER_64) as i32;
176 b_sum += (hval * (k as f64 + 0.5) * MULTIPLIER_64) as i32;
177 }
178 }
179 }
180
181 if ntot > 0 {
182 let r = r_sum / ntot;
183 let g = g_sum / ntot;
184 let b = b_sum / ntot;
185 Color::new(r as u8, g as u8, b as u8)
186 } else {
187 let r = MULTIPLIER * (self.r_min as i32 + self.r_max as i32 + 1) / 2;
188 let g = MULTIPLIER * (self.g_min as i32 + self.g_max as i32 + 1) / 2;
189 let b = MULTIPLIER * (self.b_min as i32 + self.b_max as i32 + 1) / 2;
190 Color::new(cmp::min(r, 255) as u8,
191 cmp::min(g, 255) as u8,
192 cmp::min(b, 255) as u8)
193 }
194 }
195
196 fn widest_color_channel(&self) -> ColorChannel {
197 let r_width = self.r_max - self.r_min;
198 let g_width = self.g_max - self.g_min;
199 let b_width = self.b_max - self.b_min;
200
201 let max = cmp::max(cmp::max(r_width, g_width), b_width);
202
203 if max == r_width {
204 ColorChannel::Red
205 } else if max == g_width {
206 ColorChannel::Green
207 } else {
208 ColorChannel::Blue
209 }
210 }
211}
212
213fn make_histogram_and_vbox(
214 pixels: &[u8],
215 color_format: ColorFormat,
216 step: u8,
217) -> (VBox, Vec<i32>) {
218 let mut histogram: Vec<i32> = (0..HISTOGRAM_SIZE).map(|_| 0).collect();
219
220 let mut r_min = u8::MAX;
221 let mut r_max = u8::MIN;
222 let mut g_min = u8::MAX;
223 let mut g_max = u8::MIN;
224 let mut b_min = u8::MAX;
225 let mut b_max = u8::MIN;
226
227 let colors_count = match color_format {
228 ColorFormat::Rgb => 3,
229 ColorFormat::Rgba => 4,
230 ColorFormat::Argb => 4,
231 ColorFormat::Bgr => 3,
232 ColorFormat::Bgra => 4,
233 };
234
235 let pixel_count = pixels.len() / colors_count;
236 let mut i = 0;
237 while i < pixel_count {
238 let pos = i * colors_count;
239
240 let (r, g, b, a) = color_parts(pixels, color_format, pos);
241
242 i += colors_count * step as usize;
243
244 if a < 125 || (r > 250 && g > 250 && b > 250) {
246 continue;
247 }
248
249 let shifted_r = r >> RIGHT_SHIFT as u8;
250 let shifted_b = b >> RIGHT_SHIFT as u8;
251 let shifted_g = g >> RIGHT_SHIFT as u8;
252
253 r_min = cmp::min(r_min, shifted_r);
254 r_max = cmp::max(r_max, shifted_r);
255 g_min = cmp::min(g_min, shifted_g);
256 g_max = cmp::max(g_max, shifted_g);
257 b_min = cmp::min(b_min, shifted_b);
258 b_max = cmp::max(b_max, shifted_b);
259
260 let index = make_color_index_of(shifted_r, shifted_g, shifted_b);
262 histogram[index] += 1;
263 }
264
265 let mut vbox = VBox::new(r_min, r_max, g_min, g_max, b_min, b_max);
266 vbox.recalc(&histogram);
267
268 (vbox, histogram)
269}
270
271
272fn color_parts(
274 pixels: &[u8],
275 color_format: ColorFormat,
276 pos: usize,
277) -> (u8, u8, u8, u8) {
278 match color_format {
279 ColorFormat::Rgb => {
280 (pixels[pos + 0],
281 pixels[pos + 1],
282 pixels[pos + 2],
283 255)
284 }
285 ColorFormat::Rgba => {
286 (pixels[pos + 0],
287 pixels[pos + 1],
288 pixels[pos + 2],
289 pixels[pos + 3])
290 }
291 ColorFormat::Argb => {
292 (pixels[pos + 1],
293 pixels[pos + 2],
294 pixels[pos + 3],
295 pixels[pos + 0])
296 },
297 ColorFormat::Bgr => {
298 (pixels[pos + 2],
299 pixels[pos + 1],
300 pixels[pos + 0],
301 255)
302 }
303 ColorFormat::Bgra => {
304 (pixels[pos + 2],
305 pixels[pos + 1],
306 pixels[pos + 0],
307 pixels[pos + 3])
308 }
309 }
310}
311
312fn apply_median_cut(
313 histogram: &[i32],
314 vbox: &mut VBox,
315) -> Result<(VBox, Option<VBox>), Error> {
316 if vbox.count == 0 {
317 return Err(Error::InvalidVBox);
318 }
319
320 if vbox.count == 1 {
322 return Ok((vbox.clone(), None));
323 }
324
325 let mut total = 0;
327 let mut partial_sum: Vec<i32> = (0..VBOX_LENGTH).map(|_| -1).collect();
328
329 let axis = vbox.widest_color_channel();
330 match axis {
331 ColorChannel::Red => {
332 for i in vbox.r_min..(vbox.r_max + 1) {
333 let mut sum = 0;
334 for j in vbox.g_min..(vbox.g_max + 1) {
335 for k in vbox.b_min..(vbox.b_max + 1) {
336 let index = make_color_index_of(i, j, k);
337 sum += histogram[index];
338 }
339 }
340 total += sum;
341 partial_sum[i as usize] = total;
342 }
343 }
344 ColorChannel::Green => {
345 for i in vbox.g_min..(vbox.g_max + 1) {
346 let mut sum = 0;
347 for j in vbox.r_min..(vbox.r_max + 1) {
348 for k in vbox.b_min..(vbox.b_max + 1) {
349 let index = make_color_index_of(j, i, k);
350 sum += histogram[index];
351 }
352 }
353 total += sum;
354 partial_sum[i as usize] = total;
355 }
356 }
357 ColorChannel::Blue => {
358 for i in vbox.b_min..(vbox.b_max + 1) {
359 let mut sum = 0;
360 for j in vbox.r_min..(vbox.r_max + 1) {
361 for k in vbox.g_min..(vbox.g_max + 1) {
362 let index = make_color_index_of(j, k, i);
363 sum += histogram[index];
364 }
365 }
366 total += sum;
367 partial_sum[i as usize] = total;
368 }
369 }
370 }
371
372 let mut look_ahead_sum: Vec<i32> = (0..VBOX_LENGTH).map(|_| -1).collect();
373 for (i, sum) in partial_sum.iter().enumerate().filter(|&(_, sum)| *sum != -1) {
374 look_ahead_sum[i] = total - sum;
375 }
376
377 cut(axis, vbox, histogram, &partial_sum, &look_ahead_sum, total)
378}
379
380fn cut(
381 axis: ColorChannel,
382 vbox: &VBox,
383 histogram: &[i32],
384 partial_sum: &[i32],
385 look_ahead_sum: &[i32],
386 total: i32,
387) -> Result<(VBox, Option<VBox>), Error> {
388 let (vbox_min, vbox_max) = match axis {
389 ColorChannel::Red => (vbox.r_min as i32, vbox.r_max as i32),
390 ColorChannel::Green => (vbox.g_min as i32, vbox.g_max as i32),
391 ColorChannel::Blue => (vbox.b_min as i32, vbox.b_max as i32),
392 };
393
394 for i in vbox_min..vbox_max + 1 {
395 if partial_sum[i as usize] <= total / 2 {
396 continue;
397 }
398
399 let mut vbox1 = vbox.clone();
400 let mut vbox2 = vbox.clone();
401
402 let left = i - vbox_min;
403 let right = vbox_max - i;
404
405 let mut d2 = if left <= right {
406 cmp::min(vbox_max - 1, i + right / 2)
407 } else {
408 cmp::max(vbox_min, ((i - 1) as f64 - left as f64 / 2.0) as i32)
411 };
412
413 while d2 < 0 || partial_sum[d2 as usize] <= 0 {
415 d2 += 1;
416 }
417 let mut count2 = look_ahead_sum[d2 as usize];
418 while count2 == 0 && d2 > 0 && partial_sum[d2 as usize - 1] > 0 {
419 d2 -= 1;
420 count2 = look_ahead_sum[d2 as usize];
421 }
422
423 match axis {
425 ColorChannel::Red => {
426 vbox1.r_max = d2 as u8;
427 vbox2.r_min = (d2 + 1) as u8;
428 }
429 ColorChannel::Green => {
430 vbox1.g_max = d2 as u8;
431 vbox2.g_min = (d2 + 1) as u8;
432 }
433 ColorChannel::Blue => {
434 vbox1.b_max = d2 as u8;
435 vbox2.b_min = (d2 + 1) as u8;
436 }
437 }
438
439 vbox1.recalc(histogram);
440 vbox2.recalc(histogram);
441
442 return Ok((vbox1, Some(vbox2)));
443 }
444
445 Err(Error::VBoxCutFailed)
446}
447
448fn quantize(
449 pixels: &[u8],
450 color_format: ColorFormat,
451 quality: u8,
452 max_colors: u8,
453) -> Result<Vec<Color>, Error> {
454 let (vbox, histogram) = make_histogram_and_vbox(pixels, color_format, quality);
456
457 let mut pq = vec![vbox.clone()];
459
460 let target = (FRACTION_BY_POPULATION * max_colors as f64).ceil() as u8;
462
463 iterate(&mut pq, compare_by_count, target, &histogram)?;
465
466 pq.sort_by(compare_by_product);
468
469 let len = pq.len() as u8;
471 iterate(&mut pq, compare_by_product, max_colors - len, &histogram)?;
472
473 pq.reverse();
475
476 let mut colors: Vec<Color> = pq.iter().map(|v| v.average).collect();
478 colors.truncate(max_colors as usize);
479
480 Ok(colors)
481}
482
483fn iterate<P>(
485 queue: &mut Vec<VBox>,
486 comparator: P,
487 target: u8,
488 histogram: &[i32],
489) -> Result<(), Error>
490 where P: FnMut(&VBox, &VBox) -> cmp::Ordering + Copy
491{
492 let mut color = 1;
493
494 for _ in 0..MAX_ITERATIONS {
495 if let Some(mut vbox) = queue.last().cloned() {
496 if vbox.count == 0 {
497 queue.sort_by(comparator);
498 continue;
499 }
500 queue.pop();
501
502 let vboxes = apply_median_cut(histogram, &mut vbox)?;
504 queue.push(vboxes.0.clone());
505 if let Some(ref vb) = vboxes.1 {
506 queue.push(vb.clone());
507 color += 1;
508 }
509
510 queue.sort_by(comparator);
511
512 if color >= target {
513 break;
514 }
515 }
516 }
517
518 Ok(())
519}
520
521fn compare_by_count(a: &VBox, b: &VBox) -> cmp::Ordering {
522 a.count.cmp(&b.count)
523}
524
525fn compare_by_product(a: &VBox, b: &VBox) -> cmp::Ordering {
526 if a.count == b.count {
527 a.volume.cmp(&b.volume)
529 } else {
530 let a_product = a.count as i64 * a.volume as i64;
532 let b_product = b.count as i64 * b.volume as i64;
533 a_product.cmp(&b_product)
534 }
535}
536
537fn make_color_index_of(red: u8, green: u8, blue: u8) -> usize {
539 ( ((red as i32) << (2 * SIGNAL_BITS))
540 + ((green as i32) << SIGNAL_BITS)
541 + blue as i32
542 ) as usize
543}