Fade the draft into the sharp frame when a drag settles
When a gesture stopped, the half-resolution draft was replaced by the full-resolution frame in one step, a visible jump from soft to sharp. FR-DSP-4 asks for a refinement that is smooth, not a jarring swap. The canvas now keeps the last draft frame (`canvas-previous`) and draws it over the sharp one, fading it out over 150 ms when the draft flag clears. The fade costs no render: the draft is a refcount on the texture it was drawn into, and the adjust pass ping-pongs between two output targets, so the sharp frame is written into the other one. While a gesture is drafting the layer is hidden and snapped opaque, so a new drag shows its draft at once; past the fade it is hidden again, and a settled canvas composites one image as before.
This commit is contained in:
+14
-5
@@ -2293,11 +2293,20 @@ fn build_render_now(
|
||||
match rendered {
|
||||
Ok(image) => {
|
||||
// TRACES: FR-DSP-4
|
||||
// **The draft flag goes past the render**, to the places
|
||||
// that have to know which kind of frame is up: the canvas,
|
||||
// and the histogram, which is measured on settled frames
|
||||
// only and so describes an older frame while a draft is
|
||||
// showing.
|
||||
// **The draft flag goes past the render**, to the two
|
||||
// places that have to know which kind of frame is up: the
|
||||
// canvas, which crossfades a draft into the sharp frame
|
||||
// that settles it, and the histogram, which is measured
|
||||
// on settled frames only and so describes an older frame
|
||||
// while a draft is showing.
|
||||
//
|
||||
// The draft is kept as the fade's starting picture. That
|
||||
// is a refcount on the texture it was drawn into, not a
|
||||
// copy or a render — see `canvas-previous` for why the
|
||||
// sharp frame cannot overwrite it.
|
||||
if draft {
|
||||
window.set_canvas_previous(image.clone());
|
||||
}
|
||||
window.set_canvas(image);
|
||||
window.set_canvas_draft(draft);
|
||||
window.global::<Levels>().set_provisional(draft);
|
||||
|
||||
@@ -102,6 +102,16 @@ export component AppWindow inherits Window {
|
||||
/// TRACES: FR-DSP-4
|
||||
/// Whether `canvas` is a draft: half resolution, rendered mid-gesture.
|
||||
in property <bool> canvas-draft;
|
||||
/// The last draft frame, kept for the crossfade into the sharp one.
|
||||
///
|
||||
/// Set only on draft frames, so on the frame that settles it still holds
|
||||
/// the draft that frame replaces. No render is spent on it: it is the
|
||||
/// texture the draft was already drawn into, and `AdjustPass` ping-pongs
|
||||
/// between two targets, so the sharp frame is written into the *other*
|
||||
/// one (and at a different size, which reallocates rather than
|
||||
/// overwrites). The fade is the compositor blending two textures it
|
||||
/// already holds.
|
||||
in property <image> canvas-previous;
|
||||
in property <string> adapter: "detecting…";
|
||||
in property <string> backend: "—";
|
||||
in property <string> inference-backend: "detecting…";
|
||||
@@ -1640,6 +1650,41 @@ in property <bool> panel-visible: true;
|
||||
visible: root.total > 0 && root.load-error == "";
|
||||
}
|
||||
|
||||
// TRACES: FR-DSP-4
|
||||
// **The draft, fading out over the sharp frame that
|
||||
// replaced it**, so settling reads as the picture coming
|
||||
// into focus rather than a swap.
|
||||
//
|
||||
// Hidden while drafting — it would be the same picture
|
||||
// twice — and snapped to opaque there, so the fade always
|
||||
// starts from the draft. Only the leaving transition is
|
||||
// animated: a new gesture shows its draft at once. Past
|
||||
// the fade it is transparent and hidden, so a settled
|
||||
// canvas composites exactly one image, as before.
|
||||
//
|
||||
// It fades into whatever sharp frame follows a draft. That
|
||||
// is the settle in every case but one: holding a
|
||||
// comparison within the settle delay of a drag renders the
|
||||
// original straight away, and the last draft of the edit
|
||||
// fades out over it rather than vanishing.
|
||||
previous-frame := Image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
source: root.canvas-previous;
|
||||
image-fit: contain;
|
||||
image-rendering: canvas-image.image-rendering;
|
||||
opacity: 0;
|
||||
visible: canvas-image.visible && !root.canvas-draft && self.opacity > 0;
|
||||
states [
|
||||
drafting when root.canvas-draft: {
|
||||
opacity: 1;
|
||||
out {
|
||||
animate opacity { duration: 150ms; easing: ease-out; }
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
// The region map, composited over the photograph at the
|
||||
// same fitted rect.
|
||||
//
|
||||
|
||||
Reference in New Issue
Block a user