make colour quantization more likely to include outliers

This commit is contained in:
Crispy 2024-12-05 00:32:46 +01:00
parent f547a21f00
commit 700bf9a9ef

View file

@ -20,8 +20,13 @@ fn main() {
let image = image.resize(w / 8, h / 8, FilterType::CatmullRom); let image = image.resize(w / 8, h / 8, FilterType::CatmullRom);
image.save("scaled.png").unwrap(); image.save("scaled.png").unwrap();
println!("resized"); println!("resized");
// let image = quantize_image(image, 6);
let image = dither(image, 12); const COLOR_COUNT: usize = 6;
let quantized_image = quantize_image(image.clone(), COLOR_COUNT);
quantized_image.save("q.png").unwrap();
let image = dither(image, COLOR_COUNT);
image.save("out.png").unwrap(); image.save("out.png").unwrap();
println!("saved"); println!("saved");
} }
@ -34,10 +39,15 @@ fn generate_palette(image: &DynamicImage, count: usize) -> Vec<Rgb<u8>> {
.map(|(_x, _y, a)| a.to_rgb()) // .map(|(_x, _y, a)| a.to_rgb()) //
.collect()]; .collect()];
struct Spread {
range: u8,
channel: usize,
mid_point: u8,
}
// divide buckets count-1 times // divide buckets count-1 times
for _i in 0..(count - 1) { for _i in 0..(count - 1) {
// spread amount and channel for each bucket // spread amount and channel for each bucket
let mut spreads: Vec<(u8, usize)> = Vec::new(); let mut spreads: Vec<Spread> = Vec::new();
// calculate where each bucket would do its division if it's chosen // calculate where each bucket would do its division if it's chosen
// todo: only calculate this when the bucket is created/modified // todo: only calculate this when the bucket is created/modified
for bucket in &buckets { for bucket in &buckets {
@ -54,23 +64,30 @@ fn generate_palette(image: &DynamicImage, count: usize) -> Vec<Rgb<u8>> {
let range_b = max[2] - min[2]; let range_b = max[2] - min[2];
let mut widest_channel = 0; let mut widest_channel = 0;
let mut widest_amount = range_r; let mut widest_amount = range_r;
let mut widest_middle = min[0] + range_r / 2;
if range_g > widest_amount { if range_g > widest_amount {
widest_amount = range_g; widest_amount = range_g;
widest_channel = 1; widest_channel = 1;
widest_middle = min[1] + range_g / 2;
} }
if range_b > widest_amount { if range_b > widest_amount {
widest_amount = range_b; widest_amount = range_b;
widest_channel = 2; widest_channel = 2;
widest_middle = min[2] + range_b / 2;
} }
spreads.push((widest_amount, widest_channel)) spreads.push(Spread {
range: widest_amount,
channel: widest_channel,
mid_point: widest_middle,
});
} }
let mut most_spread_bucket = 0; let mut most_spread_bucket = 0;
let mut highest_spread = 0; let mut highest_spread = 0;
for (i, &(spread, _channel)) in spreads.iter().enumerate() { for (i, spread) in spreads.iter().enumerate() {
if spread > highest_spread { if spread.range > highest_spread {
most_spread_bucket = i; most_spread_bucket = i;
highest_spread = spread; highest_spread = spread.range;
} }
} }
@ -80,11 +97,21 @@ fn generate_palette(image: &DynamicImage, count: usize) -> Vec<Rgb<u8>> {
continue; continue;
} }
let channel = spreads[most_spread_bucket].1; let channel = spreads[most_spread_bucket].channel;
bucket.sort_unstable_by_key(|pixel| pixel.0[channel]); bucket.sort_unstable_by_key(|pixel| pixel.0[channel]);
let halfway = bucket.len() / 2; let mid_value = spreads[most_spread_bucket].mid_point;
let new_bucket = bucket[halfway..].to_owned(); let pop_split = bucket.len() / 2;
bucket.truncate(halfway); let pos_split = bucket
.iter()
.position(|c| c.0[channel] >= mid_value)
.unwrap();
let split_index = if spreads[most_spread_bucket].range > 150 {
pos_split
} else {
pop_split
};
let new_bucket = bucket[split_index..].to_owned();
bucket.truncate(split_index);
buckets.push(new_bucket); buckets.push(new_bucket);
} }
let mut colors = Vec::new(); let mut colors = Vec::new();
@ -168,7 +195,7 @@ fn dither(input: DynamicImage, count: usize) -> DynamicImage {
let by = y as usize % 4; let by = y as usize % 4;
let bayer = bayer_f(bx, by); let bayer = bayer_f(bx, by);
let ratio = ratio + bayer * 1.5; let ratio = ratio + bayer;
let out_col = if ratio > 0. { best_col } else { second_col }; let out_col = if ratio > 0. { best_col } else { second_col };
out.put_pixel(x, y, out_col.to_rgba()); out.put_pixel(x, y, out_col.to_rgba());