Hold one key to see the photograph before you touched it
FR-DEV-7 asks for the current edit against the unedited original and nothing implemented it. What the develop view had was history navigation, which *changes* the edit rather than previewing against it — so the only way to look was to undo, look, and redo, and that puts two real steps on the stack at exactly the moment a photographer suspects they have overcooked a frame and is least sure of what they are doing. Holding the "Before" button, or backslash, renders the graph with every adjustment stripped and hands it straight back afterwards: the same suspend-render-restore shape the crop overlay already uses to show an uncropped frame and an export uses to suspend the zoom. Nothing is recorded, no rows are re-synced, and the photograph is still modified when the key comes up — the panel goes on describing the edit the photographer has, because only the canvas is answering a question. The framing deliberately stays on. A held comparison is a question about tone and colour, and re-cropping the canvas under someone's thumb would move the detail they are comparing; worse, the zoom is a rectangle of the *framed* image, so dropping the crop at 4× would quietly show a different part of the photograph rather than the same part unedited. What the crop took away is already compared in Compose, which shows the whole frame. Not a split screen: that halves the working image on the tablet this column was sized for, and the comparison photographers describe making is a flick back and forth rather than two pictures side by side. Press-and-hold is one gesture on a finger and on a mouse, which is what FR-DEV-3b's mapping wants, and it has no mode to be stranded in — the button reports both edges, so a press the system cancels puts the original down too.
This commit is contained in:
@@ -3605,6 +3605,72 @@ impl DevelopSession {
|
||||
Ok((image, rw, rh))
|
||||
}
|
||||
|
||||
/// TRACES: FR-DEV-7
|
||||
/// Render the photograph as the file has it, with every adjustment off.
|
||||
///
|
||||
/// **What a held comparison shows, and it is not a history state.**
|
||||
/// FR-DEV-7 asks for the current edit against the unedited original, and
|
||||
/// the only thing the interface had was [`Self::go_to_history`] — which
|
||||
/// *changes* the edit rather than previewing against it. A photographer
|
||||
/// who suspects they have overcooked a frame therefore had to undo, look,
|
||||
/// and redo, which puts two real steps on the stack at exactly the moment
|
||||
/// they are least sure of what they are doing.
|
||||
///
|
||||
/// This puts none there. It borrows the graph for the length of one
|
||||
/// render and hands it back: the same shape [`Self::render_uncropped`]
|
||||
/// uses for the crop and [`Self::render_the_file`] uses for the zoom, and
|
||||
/// for the same reason — the graph is the one description of the
|
||||
/// photograph, so a second rendering of it is a suspension rather than a
|
||||
/// copy. Nothing is recorded, nothing is marked modified, and a caller
|
||||
/// asking whether the image differs from its defaults gets the same
|
||||
/// answer before and after.
|
||||
///
|
||||
/// **The framing stays on**, and that is a decision rather than an
|
||||
/// oversight. A held before/after is a question about tone and colour —
|
||||
/// "have I pushed this too far" — and re-cropping the canvas under the
|
||||
/// photographer's thumb would move the very detail they are comparing.
|
||||
/// It would also make the view meaningless: the zoom is a rectangle of the
|
||||
/// *framed* image, so dropping the crop at 4× would show a different part
|
||||
/// of the photograph rather than the same part unedited. What the crop
|
||||
/// took away is compared in Compose, which already shows the whole frame.
|
||||
///
|
||||
/// Restored whatever happens, for the reason `render_uncropped` restores
|
||||
/// its crop: leaving the graph stripped after a failed render would
|
||||
/// discard the entire edit, silently.
|
||||
pub fn render_original(&mut self, width: u32, height: u32) -> Result<slint::Image, String> {
|
||||
let saved = self.graph.state();
|
||||
self.strip_adjustments();
|
||||
let rendered = self.render(width, height);
|
||||
let debt = self.graph.set_state(&saved);
|
||||
self.pay_film_debt(&debt);
|
||||
rendered
|
||||
}
|
||||
|
||||
/// TRACES: FR-DEV-7
|
||||
/// Take everything off the graph except the shape of the frame.
|
||||
///
|
||||
/// Four removals rather than [`EditGraph::reset`], which would take the
|
||||
/// framing with it. The empty preset applied at
|
||||
/// [`Scope::adjustments`] — the scope that is defined as "all of it but
|
||||
/// the crop" — clears every parameter the photographer can move, and the
|
||||
/// three things that are not parameters go by hand: local adjustments,
|
||||
/// repairs, and the film stock. A local adjustment is as much an edit as
|
||||
/// the slider that drives it, so an "original" still wearing its masks
|
||||
/// would be answering a different question.
|
||||
///
|
||||
/// **Only ever inside a suspension.** This leaves the graph describing a
|
||||
/// photograph nobody asked for, so every caller restores from a
|
||||
/// [`EditGraph::state`] taken first.
|
||||
fn strip_adjustments(&mut self) {
|
||||
Preset::default().apply(&mut self.graph, Scope::adjustments());
|
||||
*self.graph.masks_mut() = dr_pipeline::mask::MaskStack::new();
|
||||
*self.graph.spots_mut() = dr_pipeline::SpotSet::new();
|
||||
// Through the session rather than the graph: the baked tables live on
|
||||
// the adjust pass as well, and clearing one without the other is the
|
||||
// silent disagreement `set_film` exists to prevent.
|
||||
self.set_film(None);
|
||||
}
|
||||
|
||||
/// TRACES: FR-PLAT-AND-5 | NFR-RES-1
|
||||
/// Give back the GPU memory this session is holding only to be fast.
|
||||
///
|
||||
@@ -5531,6 +5597,47 @@ mod tests {
|
||||
assert!(!session.framing_edits_image());
|
||||
}
|
||||
|
||||
/// TRACES: FR-DEV-7 | FR-DEV-5
|
||||
/// A held comparison hands the edit straight back.
|
||||
///
|
||||
/// This is the whole difference between comparing and the
|
||||
/// undo-look-redo that had to stand in for it. Two steps on the stack, at
|
||||
/// the moment a photographer is least sure of what they are doing, was
|
||||
/// the price of looking — and this asserts the price is now nothing:
|
||||
/// the same parameters, the same history, still modified.
|
||||
#[test]
|
||||
fn showing_the_original_leaves_the_edit_exactly_as_it_was() {
|
||||
let Some(ctx) = headless() else { return };
|
||||
let (mut session, _) = grey_session(&ctx);
|
||||
|
||||
// Addressed by index, so this names no operation (FR-DEV-3a).
|
||||
let row = session.rows()[0].clone();
|
||||
session.set_param(row.op_index, row.param_index, row.maximum);
|
||||
|
||||
let edit = session.copy_settings();
|
||||
let steps = session.history_rows().len();
|
||||
assert!(!session.is_neutral(), "the premise: there is an edit");
|
||||
|
||||
session
|
||||
.render_original(64, 64)
|
||||
.expect("render the original");
|
||||
|
||||
assert_eq!(
|
||||
session.copy_settings(),
|
||||
edit,
|
||||
"every parameter comes back where it was"
|
||||
);
|
||||
assert_eq!(
|
||||
session.history_rows().len(),
|
||||
steps,
|
||||
"looking is not a step to take back"
|
||||
);
|
||||
assert!(
|
||||
!session.is_neutral(),
|
||||
"and the photograph is still modified"
|
||||
);
|
||||
}
|
||||
|
||||
/// TRACES: FR-DEV-3 | FR-CAT-8
|
||||
/// Reopening an edited photograph renders its subject mask, with no model.
|
||||
///
|
||||
|
||||
Reference in New Issue
Block a user