Re-thumbnail a photograph from its own edit
A thumbnail comes from the file's embedded preview, which is the camera's idea of the photograph and knows nothing about what has been done to it since. So a frame could be cropped, turned upright and pulled two stops back, and the grid would go on showing the original — making the library, where a photographer spends most of their time, the one view in which an edit is invisible. The render is the framed output, not the sensor: `output_size` is what a crop, a quarter turn, a flip and a straighten all act on, so a thumbnail taken from the raw frame would be the right pixels in the wrong shape and still the wrong way up. It is the same path an export takes, at a size the store wants rather than at full resolution, and always sRGB — this is a JPEG in a shard that syncs between devices and is drawn as a cell, not a file anyone is finishing. Both size classes are replaced. The store keys on the class, so refreshing only the one the grid happens to be drawing leaves the other holding the unedited preview, and a zoom across the boundary would show the edit undoing itself. Each is rendered rather than downscaled from the larger, which would be a second and worse resampler than the GPU has already applied. It runs on the way out of develop, after the sidecar write is queued and never instead of it — the edit is what must not be lost, and a render that failed must not take the save down with it. Two cases are worth the work: an edit made in this sitting, which `can-undo` records even when it ends back at neutral, and an image opened with an edit already in its sidecar and left untouched, whose cached thumbnail has never shown that edit at all. A neutral image nobody touched fails both and costs nothing. Not covered: a batch paste onto a selection, which deliberately never opens a session — there is no rendered frame to take a thumbnail from, and downloading forty RAWs to make forty is exactly what that path exists to avoid.
This commit is contained in:
@@ -280,10 +280,60 @@ pub fn save_open_edit(
|
||||
},
|
||||
};
|
||||
library_ui::start_sidecar_writes(window, library, vec![write]);
|
||||
|
||||
// TRACES: FR-CAT-9
|
||||
// And bring the grid's cell up to date, so the photograph the user
|
||||
// just finished is the photograph they see in the library.
|
||||
//
|
||||
// After the sidecar write is queued, never instead of it: the edit
|
||||
// is the thing that must not be lost, and a render that failed
|
||||
// must not take the save down with it.
|
||||
refresh_thumbnail_for(window, path, session, library);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// TRACES: FR-CAT-9
|
||||
/// Re-render the grid's thumbnail from the edit that is being saved.
|
||||
///
|
||||
/// # When it is worth doing
|
||||
///
|
||||
/// Two cases, and the second is the one that is easy to miss.
|
||||
///
|
||||
/// An edit made in this sitting is the obvious one — `can-undo` is the app's
|
||||
/// own record that something was changed, and it stays true for a change that
|
||||
/// happened to end at neutral, which still needs the thumbnail putting back.
|
||||
///
|
||||
/// The other is an image opened with an edit already in its sidecar and left
|
||||
/// untouched. Nothing changed, so there is nothing to *re-*render — but the
|
||||
/// cached thumbnail came from the file's embedded preview and has never shown
|
||||
/// that edit at all. `is_neutral` is what distinguishes it: a graph that does
|
||||
/// something, against a thumbnail that shows none of it.
|
||||
///
|
||||
/// A neutral image nobody touched fails both and costs nothing, which is the
|
||||
/// common case on a scroll through a library.
|
||||
fn refresh_thumbnail_for(
|
||||
window: &AppWindow,
|
||||
path: &str,
|
||||
session: &Rc<RefCell<Option<DevelopSession>>>,
|
||||
library: &Rc<library_ui::LibraryController>,
|
||||
) {
|
||||
let worth_it = window.get_can_undo()
|
||||
|| session.borrow().as_ref().is_some_and(|s| !s.is_neutral());
|
||||
if !worth_it {
|
||||
return;
|
||||
}
|
||||
|
||||
// The borrow is held across the renders, which is safe here and would not
|
||||
// be if this were reachable from a Slint callback that also touches the
|
||||
// session — it is not: the only caller is on the way out of the view.
|
||||
let mut borrow = session.borrow_mut();
|
||||
let Some(open) = borrow.as_mut() else {
|
||||
return;
|
||||
};
|
||||
library_ui::refresh_thumbnail(window, library, path, |edge| open.render_thumbnail(edge));
|
||||
}
|
||||
|
||||
/// Load a stored edit into the open session and refresh the panel.
|
||||
///
|
||||
/// Returns whether anything was applied, so the caller can skip a redraw for
|
||||
|
||||
Reference in New Issue
Block a user