Compare commits

...
Author SHA1 Message Date
Brandon Thomas d993b0a010 Merge remote-tracking branch 'origin/main' into finish-836 2026-10-07 20:30:24 -04:00
bflatasticandClaude Opus 5.5 3515e82107 Filter previews stay inside the selection on large documents (#740)
The live on-canvas filter preview runs the real command on a downsampled
proxy of the document, but the proxy dropped the selection, so on documents
large enough to need a proxy (over ~5 MP) the preview filtered the whole
layer while OK only changed the selection. The proxy now downsamples the
selection with the pixels, so the preview is limited to the (feathered)
selection exactly like the result.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-08 04:56:54 +07:00
2 changed files with 37 additions and 2 deletions

No files matched your search

+16 -2
View File
@@ -62,7 +62,9 @@ fn shrink_layer(l: &mut Layer, k: u32) {
}
}
/// A copy of `doc` scaled down by `k` (same layer ids, so adjustments can be swapped in).
/// A copy of `doc` scaled down by `k` (same layer ids, so adjustments can be swapped in). The
/// selection scales with it, so a filter previewed on the proxy stays inside the (feathered)
/// selection exactly as the full-size result does.
pub fn proxy_document(doc: &Document, k: u32) -> Document {
let mut p = doc.clone();
if k <= 1 {
@@ -76,7 +78,7 @@ pub fn proxy_document(doc: &Document, k: u32) -> Document {
for c in &mut p.channels {
c.surface = downsample(&c.surface, k);
}
p.selection = None;
p.selection = p.selection.as_ref().map(|s| downsample(s, k));
p
}
@@ -120,4 +122,16 @@ mod tests {
assert_eq!(p.layers[0].surface().unwrap().pixel(999, 749), vec![1.0; 4]);
assert!(proxy_faithful(&doc));
}
#[test]
fn proxy_scales_the_selection() {
let mut doc = Document::with_background("d", Size::new(64, 64), ColorMode::Rgb, SampleType::U8, Color::WHITE);
let mut sel = Surface::new(PixelFormat::GRAY8);
sel.fill_rect(Rect::new(0, 0, 32, 64), &[1.0]);
doc.selection = Some(sel);
let p = proxy_document(&doc, 4);
let s = p.selection.as_ref().expect("selection kept");
assert_eq!((s.pixel(7, 15)[0], s.pixel(8, 0)[0]), (1.0, 0.0));
assert!(proxy_document(&Document::new("n", Size::new(8, 8), ColorMode::Rgb, SampleType::U8), 4).selection.is_none());
}
}
+21
View File
@@ -550,4 +550,25 @@ mod tests {
assert!(p[0] > 0.2 && p[0] < 0.8, "edge blurred: {p:?}");
assert_eq!(label("wavelengthMin"), "Wavelength Min");
}
#[test]
fn preview_stays_inside_the_selection_at_every_proxy_factor() {
use photocraft_doc::SampleType;
for depth in [SampleType::U8, SampleType::U16, SampleType::F32] {
let mut doc =
Document::with_background("p", photocraft_doc::Size::new(64, 64), photocraft_doc::ColorMode::Rgb, depth, photocraft_doc::Color::gray(0.5));
let bg = doc.layers[0].id;
let mut sel = photocraft_raster::Surface::new(photocraft_doc::PixelFormat::GRAY8);
sel.fill_rect(photocraft_geom::Rect::new(0, 0, 32, 64), &[1.0]);
doc.selection = Some(sel);
for k in [1, 2, 4] {
let out = preview_document(&doc, Some(bg), "filter.noise.addNoise", &json!({"amount": 100.0}), k).unwrap();
let s = out.layers[0].surface().unwrap();
let half = 32 / k as i32;
let changed = |x0: i32, x1: i32| (0..64 / k as i32).any(|y| (x0..x1).any(|x| s.pixel(x, y) != doc.layers[0].surface().unwrap().pixel(0, 0)));
assert!(changed(0, half), "{depth:?} k={k}: noise inside the selection");
assert!(!changed(half, 64 / k as i32), "{depth:?} k={k}: nothing outside the selection");
}
}
}
}