From b6a95b6c8d0b84e60b1a25822dd3e31976a4e4da Mon Sep 17 00:00:00 2001 From: storybored379 Date: Sat, 3 Oct 2026 21:57:38 -0700 Subject: [PATCH] Indexed Color: no panic when forced colours fill the palette (#17) Image > Mode > Indexed Color with Colors 2 + Forced Black and White (or 8 + Primaries, or <=216 + Web) leaves no room for computed entries. median_cut(.., 0) returns no centres and kmeans then indexed into the empty list ("index out of bounds: the len is 0 but the index is 0"), taking the command down with a panic. kmeans now returns early when there are no centres, so the palette is just the forced colours. 1 test (algo quantize). Co-authored-by: storybored379 Co-authored-by: Claude Opus 5.5 (1M context) --- crates/algo/src/quantize.rs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/crates/algo/src/quantize.rs b/crates/algo/src/quantize.rs index 4851e15293..01b44b24f5 100644 --- a/crates/algo/src/quantize.rs +++ b/crates/algo/src/quantize.rs @@ -247,6 +247,10 @@ fn median_cut(pts: &[([f32; 3], u32)], n: usize) -> Vec<[f32; 3]> { /// Lloyd refinement of `centers` over weighted points. fn kmeans(pts: &[([f32; 3], u32)], centers: &mut [[f32; 3]], iters: usize) { + if centers.is_empty() { + // No room left (the forced colours fill the palette): nothing to refine. + return; + } for _ in 0..iters { let mut sum = vec![[0.0f64; 3]; centers.len()]; let mut cnt = vec![0.0f64; centers.len()]; @@ -690,6 +694,24 @@ mod tests { } } + #[test] + fn forced_colours_filling_the_count_do_not_panic() { + // 2 colours + Black and White (or 8 + Primaries) leaves no room for + // computed entries; the median cut used to return no centres and + // k-means indexed into the empty list. + let px = vec![[1.0, 1.0, 1.0, 1.0]; 16]; + for kind in [ + PaletteKind::Adaptive, + PaletteKind::Perceptual, + PaletteKind::Selective, + ] { + let pal = build_palette(&px, kind, 2, Forced::BlackWhite).unwrap(); + assert_eq!(pal, vec![[0, 0, 0], [255, 255, 255]], "{kind:?}"); + let pal = build_palette(&gradient(8, 8), kind, 8, Forced::Primaries).unwrap(); + assert_eq!(pal.len(), 8, "{kind:?}"); + } + } + #[test] fn quantize_maps_to_palette_with_each_dither() { let pal = vec![[0, 0, 0], [255, 255, 255]];