From b28ce8ef153c76e5834da47d08687b0c3e961f1b Mon Sep 17 00:00:00 2001 From: kelanna Date: Sun, 11 Oct 2026 09:39:40 -0700 Subject: [PATCH] LR-PREF-DISPLAY: zoomed views stay sharp with a monitor profile With a display profile set, the 1:1 (and closer) Detail and Compare views showed only the whole-frame render magnified, not the sharp window cut from the photo's own pixels. The renderer gives a display-profile render its own key (RenderJob::with_display), but the window tiles were recorded under the key the job had before, so draw_window never found the texture. The tiles are now keyed with the job as the renderer runs it (Renderer::job_for), in Detail and in Compare. Tests: a_display_profile_keeps_the_sharp_window_at_one_to_one and a_display_profile_keeps_the_compare_windows_at_one_to_one check that the window is painted over the whole-frame render; both failed before the fix. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01WEuxxPyG8t6KcADbZSEAUj --- crates/ui-egui/src/panels/compare.rs | 2 + crates/ui-egui/src/panels/detail.rs | 2 + crates/ui-egui/src/tests_preview_limit.rs | 47 +++++++++++++++++++++++ 3 files changed, 51 insertions(+) diff --git a/crates/ui-egui/src/panels/compare.rs b/crates/ui-egui/src/panels/compare.rs index 4d5e7453b..933b87415 100644 --- a/crates/ui-egui/src/panels/compare.rs +++ b/crates/ui-egui/src/panels/compare.rs @@ -350,6 +350,8 @@ fn request_window(app: &mut LightcraftApp, v: &TileView) -> Option Opt let overlay = if v.before { lightcraft_pipeline::Overlay::None } else { c.overlay }; let job = if v.before { job } else { job.with_hidden_grading(app.ui.grading_hidden_for(c.id)) }; let job = job.with_overlay(overlay); + // keyed as the renderer keys the texture (a display profile changes the key) + let job = app.renderer.job_for(v.slot, job); // The overlay is part of the tile's identity too, so changing mask view/colour never leaves a // stale region tile on screen while its replacement renders. let look = job.settings.hash64() ^ overlay.key().rotate_left(17); diff --git a/crates/ui-egui/src/tests_preview_limit.rs b/crates/ui-egui/src/tests_preview_limit.rs index 3b712e876..fd1f0e447 100644 --- a/crates/ui-egui/src/tests_preview_limit.rs +++ b/crates/ui-egui/src/tests_preview_limit.rs @@ -763,6 +763,53 @@ mod in_the_loupe { assert!(held(&h) > before / 3, "and they are still there"); } + /// Load a Display P3 monitor profile (`app.displayProfile`), as Settings ▸ Display does. + fn use_display_profile(h: &mut Headless) { + let dir = std::env::temp_dir().join(format!("lightcraft-zoom-display-{}-{:?}", std::process::id(), std::thread::current().id())); + std::fs::create_dir_all(&dir).expect("temp dir"); + let path = dir.join("p3.icc"); + std::fs::write(&path, lightcraft_codecs::icc::write_named(lightcraft_codecs::NamedSpace::DisplayP3)).expect("profile written"); + let r = h.request("engine.execute", json!({"command": "app.displayProfile", "params": {"path": path.to_string_lossy()}}), T); + assert_eq!(r["ok"], true, "{r}"); + assert!(h.app.renderer.display_id().is_some()); + } + + // Given a monitor profile, when I zoom to 1:1, then the sharp window is painted over the + // whole-frame render, as without one (a display profile changes the texture's render key, and + // the window used to be looked up by the key it had before: never found, so only the + // whole-frame render, magnified and blurred, was shown) + #[test] + fn a_display_profile_keeps_the_sharp_window_at_one_to_one() { + let (mut h, native) = detail(); + use_display_profile(&mut h); + h.request("engine.execute", json!({"command": "view.zoom100"}), T); + h.settle(SETTLE); + assert!(rendered_long_edge(&h) < native, "the whole frame is smaller than the photo"); + let view = h.app.region_view.expect("a window render"); + assert_eq!(view.full.0.max(view.full.1), native); + let (whole, window) = (drawn(&h, Slot::Main), drawn(&h, Slot::Region)); + assert!(!whole.is_empty(), "the whole-frame render is painted"); + assert!(!window.is_empty(), "the window is painted"); + assert!(window[0].0 > whole[0].0, "over the whole-frame render"); + } + + // …and in Compare, each pane's window too + #[test] + fn a_display_profile_keeps_the_compare_windows_at_one_to_one() { + let (mut h, _) = detail(); + use_display_profile(&mut h); + let r = h.request("engine.execute", json!({"command": "view.compare"}), T); + assert_eq!(r["ok"], true, "{r}"); + h.app.ui.zoom = crate::state::Zoom::Percent(100.0); + h.settle(SETTLE); + for i in 0..2u8 { + assert!(h.app.compare_windows[i as usize].is_some(), "pane {i} asked for a window"); + let (whole, window) = (drawn(&h, Slot::Compare(i)), drawn(&h, Slot::CompareRegion(i))); + assert!(!whole.is_empty() && !window.is_empty(), "pane {i}: both are painted"); + assert!(window[0].0 > whole[0].0, "pane {i}: the window is painted over the whole-frame render"); + } + } + /// Compare view of the demo library at `zoom`, settled. fn compare_at(zoom: crate::state::Zoom) -> Headless { let (mut h, _) = detail();