mirror of
https://github.com/storytold/lightcraft.git
synced 2026-10-26 19:17:02 -06:00
ui: render the Show All Masks overlay in zoom windows
The loupe's window rendering already evaluates every visible mask's alpha for an `Overlay::AllMasks` (its union is composed from the same alphas the selected-mask overlay uses), but `overlay_supports_window` refused it, so at 1:1 and closer "Show All Masks" fell back to a whole-frame render at the zoomed size: slower, and its detail stopped at the frame render's resolution. Allowing it makes the zoomed view a canvas-sized frame plus a native window, as a single mask's overlay already is. Tests: - tests_preview_limit::an_all_masks_overlay_at_one_to_one_is_a_window (fails before: "native 6000: the whole-frame render is 6000") - tests_window::an_all_masks_overlay_window_matches_the_whole_render (a window of the union matches the whole render's pixels, for every view)
This commit is contained in:
@@ -238,6 +238,38 @@ fn a_mask_overlay_window_matches_the_whole_render() {
|
||||
}
|
||||
}
|
||||
|
||||
// Given the Show All Masks view (issue #1712), a window of it matches the same pixels of the
|
||||
// whole render with that overlay: the union is composed from the same mask alphas, so the zoomed
|
||||
// loupe can show it as a window like a single mask's overlay (issue #547)
|
||||
#[test]
|
||||
fn an_all_masks_overlay_window_matches_the_whole_render() {
|
||||
use crate::{MaskView, Overlay};
|
||||
use lightcraft_develop::{LocalAdjustments, Mask, MaskComponent, MaskOp, MaskShape};
|
||||
use lightcraft_geom::Point;
|
||||
let shapes = [
|
||||
(3, MaskShape::Radial { center: Point::new(0.45, 0.5), rx: 0.2, ry: 0.15, angle: 20.0, feather: 50.0, invert: false }, 0.0),
|
||||
(4, MaskShape::Linear { start: Point::new(0.4, 0.55), end: Point::new(0.6, 0.5) }, 100.0),
|
||||
];
|
||||
let mut s = DevelopSettings::default();
|
||||
for (id, shape, refine) in shapes {
|
||||
s.masks.push(Mask {
|
||||
id,
|
||||
components: vec![MaskComponent { name: None, op: MaskOp::Add, invert: false, shape }],
|
||||
adjust: LocalAdjustments { exposure: 1.0, ..Default::default() },
|
||||
refine,
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
for view in [MaskView::Color, MaskView::WhiteOnBlack, MaskView::ImageOnBlack] {
|
||||
let overlay = Overlay::AllMasks { view, color: [255, 0, 0], opacity: 70 };
|
||||
let f = render(&noisy_scene(), &SourceInfo::default(), &s, &RenderRequest { overlay, ..RenderRequest::fit(W, H) }).image;
|
||||
let w = render(&noisy_scene(), &SourceInfo::default(), &s, &RenderRequest { overlay, window: Some(MID), ..RenderRequest::fit(W, H) }).image;
|
||||
let (max, mean) = compare(&f, &w, MID);
|
||||
// (refine edges differs by a rounding step, as in `mask_edge_refinement_does_not_depend_on_the_window`)
|
||||
assert!(max <= 3 && mean <= 0.25, "{view:?}: max {max} (≤ 3), mean {mean:.3} (≤ 0.25)");
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Deep zoom: the wide stages' kernels are far wider than any margin -------------------------
|
||||
|
||||
/// Hard-edged blocks of very different brightness at several scales: the wide stages' edge-aware
|
||||
|
||||
@@ -250,10 +250,12 @@ fn animated_rect(ctx: &egui::Context, anim: &mut bool, target: Rect) -> Rect {
|
||||
}
|
||||
|
||||
/// Whether an overlay can be rendered on a region tile without changing what the tile means.
|
||||
/// Mask overlays are evaluated from the same mask alpha as the full render; the other diagnostic
|
||||
/// overlays either need whole-frame context or deliberately replace the loupe.
|
||||
/// Mask overlays (one mask, or every visible mask at once) are evaluated from the same mask
|
||||
/// alphas as the full render; the other diagnostic overlays either need whole-frame context or
|
||||
/// deliberately replace the loupe.
|
||||
pub(crate) fn overlay_supports_window(overlay: lightcraft_pipeline::Overlay) -> bool {
|
||||
matches!(overlay, lightcraft_pipeline::Overlay::None | lightcraft_pipeline::Overlay::Mask { .. })
|
||||
use lightcraft_pipeline::Overlay;
|
||||
matches!(overlay, Overlay::None | Overlay::Mask { .. } | Overlay::AllMasks { .. })
|
||||
}
|
||||
|
||||
/// What a window render of the loupe needs to know about the view it belongs to.
|
||||
|
||||
@@ -227,6 +227,40 @@ mod in_the_loupe {
|
||||
assert_eq!(h.app.region_view.map(|r| r.settings), Some(plain.settings), "back to the plain window");
|
||||
}
|
||||
|
||||
// Given the Show All Masks view (issue #1712) at 1:1, the zoomed loupe is a canvas-sized frame
|
||||
// plus a native window carrying the overlay, as a selected mask's is: the union is evaluated
|
||||
// from the same mask alphas, so it must not fall back to a whole-frame render at the zoomed
|
||||
// size (slower, and its detail stops at the frame render's resolution)
|
||||
#[test]
|
||||
fn an_all_masks_overlay_at_one_to_one_is_a_window() {
|
||||
let (mut h, native) = detail();
|
||||
for (command, params) in [
|
||||
("panel.masking", json!({})),
|
||||
("mask.add", json!({"kind": "radial", "center": [0.4, 0.5], "rx": 0.2, "ry": 0.2})),
|
||||
("mask.add", json!({"kind": "linear", "start": [0.2, 0.3], "end": [0.8, 0.7]})),
|
||||
] {
|
||||
let r = h.request("engine.execute", json!({"command": command, "params": params}), T);
|
||||
assert_eq!(r["ok"], true, "{command}: {r}");
|
||||
}
|
||||
let r = h.request("engine.execute", json!({"command": "view.maskOverlayAll", "params": {}}), T);
|
||||
assert_eq!(r["ok"], true, "{r}");
|
||||
assert!(h.app.ui.mask_overlay_all, "the all-masks overlay is on");
|
||||
h.request("engine.execute", json!({"command": "view.zoom100"}), T);
|
||||
h.settle(SETTLE);
|
||||
let id = h.app.session.active().expect("a photo");
|
||||
let d = h.app.session.develop_of(id).expect("settings");
|
||||
assert!(
|
||||
matches!(crate::panels::detail::view_overlay(&h.app, &d), lightcraft_pipeline::Overlay::AllMasks { .. }),
|
||||
"the all-masks overlay is shown"
|
||||
);
|
||||
let main = rendered_long_edge(&h);
|
||||
assert!(main <= 2560 && main < native, "native {native}: the whole-frame render is {main}");
|
||||
let region = h.app.region_view.expect("a window render with the all-masks overlay");
|
||||
assert_eq!(region.full.0.max(region.full.1), native, "cut from the photo's own pixels");
|
||||
let (w, hh) = region_tile(&h).expect("its texture");
|
||||
assert!(w >= 1300 && hh >= 700, "it covers the canvas: {w}×{hh}");
|
||||
}
|
||||
|
||||
// Given the user capped the preview at 1600 px, then 1:1 is rendered no larger than that
|
||||
#[test]
|
||||
fn an_explicit_limit_still_caps_one_to_one() {
|
||||
|
||||
Reference in New Issue
Block a user