Files
photocraft/crates/ui-egui/src/proxy.rs
T
Michael HigginsandClaude Opus 5.5 3aaffdde68 Adjustment layers: a live preview is never coarser than the screen (#5764) (#5778)
Dragging a Curves point (or any control) in the Properties panel of an
adjustment layer previews through `canvas::ensure_proxy_preview`, which
picked its downscale factor from the document size alone
(`proxy::preview_factor`): any document over 5 MP was drawn from a 2-5x
reduced copy at every zoom, so at 100 % the canvas turned blocky until
the mouse was released.

Cap the factor by the view's zoom with the rule the Image > Adjustments
dialogs already use (`adjust_preview::proxy_factor`: the proxy stays at
least at screen resolution) when the wgpu compositor can draw the
full-size document. At 50 % and closer the drag now shows the full-size
composite; zoomed out, the proxy still saves the work. Documents the GPU
compositor doesn't cover keep the old proxy (the full size would be a
CPU composite per change).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 22:46:20 -04:00

84 lines
3.6 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Downsampled proxy documents for fast interactive previews (e.g. dragging an adjustment slider on
//! a 36 MP image). Built once per document revision; only adjustment parameters change while the
//! slider moves, so the proxy's pixels stay valid and each preview frame composites ~1/k² pixels.
use photocraft_doc::Document;
/// Target pixel count for previews (fast enough to composite every frame on CPU).
pub const PREVIEW_PIXELS: u64 = 2_500_000;
/// Downscale factor for a document, or 1 when it's already small.
pub fn factor(doc: &Document) -> u32 {
let px = doc.size.area();
if px <= PREVIEW_PIXELS * 2 {
return 1;
}
((px as f64 / PREVIEW_PIXELS as f64).sqrt().ceil() as u32).max(2)
}
/// Whether live previews may run on a reduced copy (Preferences › Performance › Low Resolution
/// Previews, on by default). Off, every live preview renders at full resolution.
pub fn reduced_previews(app: &crate::PhotocraftApp) -> bool {
app.session.prefs().performance.low_resolution_previews
}
/// Downscale factor of a live preview of `doc`: [`factor`], or 1 when reduced previews are off.
pub fn preview_factor(doc: &Document, reduced: bool) -> u32 {
if reduced { factor(doc) } else { 1 }
}
/// A live preview's factor `k` capped for a view at `zoom` (physical screen pixels per document
/// pixel): never coarser than the screen ([`crate::adjust_preview::proxy_factor`]), so a view at
/// 100 % shows full resolution instead of k-pixel blocks while a control moves (#5764).
pub fn view_factor(k: u32, zoom: f32) -> u32 {
k.min(crate::adjust_preview::proxy_factor(zoom)).max(1)
}
pub use photocraft_compose::proxy::{downsample, proxy_document};
#[cfg(test)]
mod tests {
use super::*;
use photocraft_color::{Color, ColorMode, SampleType};
use photocraft_doc::Size;
#[test]
fn factor_scales_with_size() {
let small = Document::new("s", Size::new(1000, 1000), ColorMode::Rgb, SampleType::U8);
assert_eq!(factor(&small), 1);
let big = Document::new("b", Size::new(6016, 6016), ColorMode::Rgb, SampleType::U8);
let k = factor(&big);
assert!((3..=5).contains(&k), "{k}");
assert!(big.size.area() / (k as u64 * k as u64) <= PREVIEW_PIXELS);
assert_eq!(preview_factor(&big, true), k);
assert_eq!(preview_factor(&big, false), 1);
assert_eq!(preview_factor(&small, true), 1);
}
/// #5764: dragging a Curves point on a large document showed the canvas in k-pixel blocks at
/// any zoom. The proxy may only be as coarse as the screen.
#[test]
fn view_factor_never_coarser_than_the_screen() {
let big = Document::new("b", Size::new(6016, 6016), ColorMode::Rgb, SampleType::U8);
let k = preview_factor(&big, true);
assert!(k > 2, "{k}");
assert_eq!(view_factor(k, 1.0), 1, "100 %: full resolution");
assert_eq!(view_factor(k, 2.0), 1, "zoomed in: full resolution");
assert_eq!(view_factor(k, 0.6), 1);
assert_eq!(view_factor(k, 0.5), 2);
assert_eq!(view_factor(k, 0.1), k, "far out: the document's proxy");
assert_eq!(view_factor(1, 0.1), 1, "small documents stay full size");
assert_eq!(view_factor(k, f32::NAN), 1);
assert_eq!(view_factor(0, 0.1), 1);
}
#[test]
fn proxy_keeps_structure_and_ids() {
let doc = Document::with_background("d", Size::new(4000, 3000), ColorMode::Rgb, SampleType::U8, Color::WHITE);
let p = proxy_document(&doc, 4);
assert_eq!((p.size.width, p.size.height), (1000, 750));
assert_eq!(p.layers[0].id, doc.layers[0].id);
assert_eq!(p.layers[0].surface().unwrap().pixel(999, 749), vec![1.0; 4]);
}
}