use std::cmp::Reverse;
use std::ops::Range;
use std::path::Path;
use crabshell_ui::{Color, Fit};
use crate::icons::halve;
use crate::images::{self, Picture};
const MAX_DEPTH: u8 = 8;
pub fn quantize_colors(path: &Path, depth: u8, size: u32) -> Result<Vec<Color>, String> {
let image = match images::decode(path)? {
Picture::Pixels(mut image) => {
while size > 0 && image.width().max(image.height()) > size {
image = halve(image.as_ref())?;
}
image
}
Picture::Svg(tree) => {
let (width, height) = (tree.size().width(), tree.size().height());
let shrink = match size {
0 => 1.0,
size => (size as f32 / width.max(height)).min(1.0),
};
let (width, height) = (
(width * shrink).ceil() as u32,
(height * shrink).ceil() as u32,
);
images::check(width, height)?;
images::svg(&tree, width, height, Fit::Fill)?
}
};
let pixels = image
.pixels()
.iter()
.filter(|pixel| pixel.alpha() >= 128)
.map(|pixel| {
let color = pixel.demultiply();
[color.red(), color.green(), color.blue()]
})
.collect();
Ok(median_cut(pixels, depth))
}
struct Cut {
run: Range<usize>,
channel: usize,
spread: u8,
}
impl Cut {
fn new(pixels: &[[u8; 3]], run: Range<usize>) -> Cut {
let (mut low, mut high) = ([u8::MAX; 3], [0; 3]);
for pixel in &pixels[run.clone()] {
for channel in 0..3 {
low[channel] = low[channel].min(pixel[channel]);
high[channel] = high[channel].max(pixel[channel]);
}
}
let spread = |channel: usize| high[channel].saturating_sub(low[channel]);
let channel = (0..3).max_by_key(|&channel| spread(channel)).unwrap_or(0);
Cut {
run,
channel,
spread: spread(channel),
}
}
}
fn median_cut(mut pixels: Vec<[u8; 3]>, depth: u8) -> Vec<Color> {
let mut cuts = Vec::new();
if !pixels.is_empty() {
cuts.push(Cut::new(&pixels, 0..pixels.len()));
}
while cuts.len() < 1 << depth.min(MAX_DEPTH) {
let Some(index) = (0..cuts.len())
.filter(|&index| cuts[index].spread > 0)
.max_by_key(|&index| cuts[index].spread)
else {
break;
};
let (run, channel) = (cuts[index].run.clone(), cuts[index].channel);
let values = &mut pixels[run.clone()];
values.sort_unstable_by_key(|pixel| pixel[channel]);
let median = values[values.len() / 2][channel];
let mut split = values.partition_point(|pixel| pixel[channel] < median);
if split == 0 {
split = values.partition_point(|pixel| pixel[channel] <= median);
}
let middle = run.start + split;
cuts[index] = Cut::new(&pixels, run.start..middle);
cuts.push(Cut::new(&pixels, middle..run.end));
}
let mut colors: Vec<(usize, Color)> = cuts
.iter()
.map(|cut| {
let values = &pixels[cut.run.clone()];
let count = values.len() as u64;
let mut sums = [0_u64; 3];
for pixel in values {
for (sum, &value) in sums.iter_mut().zip(pixel) {
*sum += u64::from(value);
}
}
let [r, g, b] = sums.map(|sum| ((sum + count / 2) / count) as u8);
(values.len(), Color::rgb(r, g, b))
})
.collect();
colors.sort_by_key(|&(count, color)| (Reverse(count), [color.r, color.g, color.b]));
colors.into_iter().map(|(_, color)| color).collect()
}