Thumbnail a composite during the merge, as develop first shows it
The DNG a merge writes has no embedded preview, and an embedded preview is all the grid's thumbnail path reads, so a composite stood in the grid as a blank cell until it was opened. Reading an 800 MB file back to make one would cost what the merge already has in hand. The bands are box-reduced as they are written, after the border fill, into a copy 4096 pixels long. That copy is written as a linear DNG in memory with the composite's own profile, header and crop, and opened through open_session, the function develop opens every file with: the same decode, the default graph and view transform, the as-shot white balance and the conversion to the display's space. The grid and large thumbnails are rendered from that session, staged beside the payload before the rename releases it to a drain, and put in the store under the file id once the upload has learned it. Until then the grid draws them from memory, so the cell is not blank while the file is uploading. A test develops a synthetic composite both ways, the whole file as develop opens it and the merge's reduced copy, and holds the thumbnail's mean, 95th and 99.5th luma percentiles to within 3-4 levels of develop's render; the naive balanced-and-gamma picture the merge's preview draws misses by 13.
This commit is contained in:
@@ -467,6 +467,43 @@ pub(super) fn load_window(window: &AppWindow, ctl: &Rc<LibraryController>) {
|
||||
hold_thumbnails(&previous, &ids, &classes)
|
||||
};
|
||||
|
||||
// A composite a merge has just catalogued is drawn from the merge's own
|
||||
// thumbnails until the store can serve it (FR-MRG-6), and counts as
|
||||
// served at that class so no fetch is spent on a file still uploading.
|
||||
let wanted = dr_thumbs::ThumbSize::for_cell(
|
||||
window.global::<Library>().get_library_cell_size().max(1.0) as u32,
|
||||
);
|
||||
let mut held = held;
|
||||
{
|
||||
let merged = ctl.merged_thumbs.borrow();
|
||||
for c in &cells {
|
||||
let Some(made) = merged.get(&c.image_id) else {
|
||||
continue;
|
||||
};
|
||||
if held
|
||||
.get(&c.image_id)
|
||||
.is_some_and(|h| h.class == Some(wanted))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
let pick = made
|
||||
.iter()
|
||||
.find(|(class, _)| *class == wanted)
|
||||
.or_else(|| made.iter().max_by_key(|(class, _)| class.edge()));
|
||||
if let Some((class, image)) = pick {
|
||||
held.insert(
|
||||
c.image_id,
|
||||
Held {
|
||||
thumbnail: image.clone(),
|
||||
has_thumb: true,
|
||||
unavailable: false,
|
||||
class: Some(*class),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let rows: Vec<LibraryCell> = cells
|
||||
.iter()
|
||||
.zip(headings)
|
||||
@@ -989,7 +1026,7 @@ fn drain_thumbnails(
|
||||
/// This is a CPU copy, which is acceptable here and not in the develop path:
|
||||
/// a 256px thumbnail is 256 KB and happens once per image, where the canvas
|
||||
/// would pay per frame (ARCH §6.1).
|
||||
fn to_slint_image(width: u32, height: u32, rgba: &[u8]) -> slint::Image {
|
||||
pub(super) fn to_slint_image(width: u32, height: u32, rgba: &[u8]) -> slint::Image {
|
||||
let mut buf = slint::SharedPixelBuffer::<slint::Rgba8Pixel>::new(width, height);
|
||||
let expected = (width as usize) * (height as usize) * 4;
|
||||
let src = &rgba[..expected.min(rgba.len())];
|
||||
|
||||
Reference in New Issue
Block a user