Crop: a W x H crop on an Indexed Color document resamples with Nearest Neighbor (#7722)

The Crop tool's W x H x Resolution mode resampled with the chosen filter
(Bicubic by default) even on an Indexed Color document, so the result held
blends outside the colour table. Like Image Size (#6019), it now falls back
to Nearest Neighbor there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
David Mann
2026-10-19 13:28:32 +02:00
co-authored by Claude Opus 5.5
parent d95fdf3181
commit d20a0782da
+29 -1
View File
@@ -531,7 +531,9 @@ impl CropTarget {
/// Applies the target to a document that has just been cropped (inside the crop's history step).
fn apply(&self, doc: &mut Document) {
if let Some((w, h)) = self.size {
resample_doc(doc, w, h, self.filter);
// As for Image Size: an Indexed Color document holds only its colour table's colours (#6019).
let filter = if doc.mode == ColorMode::Indexed { Resample::Nearest } else { self.filter };
resample_doc(doc, w, h, filter);
}
if let Some(r) = self.resolution {
doc.resolution_dpi = r;
@@ -1420,6 +1422,32 @@ mod tests {
assert_eq!((doc(&s).size, doc(&s).resolution_dpi), (Size::new(30, 10), 150.0));
}
/// #7722: a W x H crop of an Indexed Color document resamples like Image Size (#6019), with
/// Nearest Neighbor, so no pixel gets a blend that isn't in the colour table.
#[test]
fn crop_target_resamples_indexed_color_with_nearest_neighbor() {
let mut s = Session::new();
s.execute("file.new", json!({"width": 2, "height": 1})).unwrap();
s.execute("edit.fill", json!({"color": "#ff0000"})).unwrap();
s.execute("select.rect", json!({"x": 1, "y": 0, "width": 1, "height": 1})).unwrap();
s.execute("edit.fill", json!({"color": "#0000ff"})).unwrap();
s.execute("select.deselect", json!({})).unwrap();
s.execute("image.mode.indexedColor", json!({})).unwrap();
for resample in [None, Some("bicubic"), Some("automatic"), Some("bilinear"), Some("lanczos")] {
let mut p = json!({"x": 0, "y": 0, "width": 2, "height": 1, "targetWidth": 8, "targetHeight": 1});
if let Some(r) = resample {
p["resample"] = json!(r);
}
s.execute("image.crop", p).unwrap();
let surf = doc(&s).layers[0].surface().unwrap();
for x in 0..8 {
let px = surf.pixel(x, 0);
assert!(px[..3] == [1.0, 0.0, 0.0] || px[..3] == [0.0, 0.0, 1.0], "{resample:?}: no blended colour at x {x}: {px:?}");
}
s.execute("history.undo", json!({})).unwrap();
}
}
#[test]
fn crop_target_accepts_whole_number_floats() {
// `json.dumps(100.0)` and many other JSON writers send whole numbers as floats.