diff --git a/crates/engine/src/files.rs b/crates/engine/src/files.rs index dc79b8b31..ea3b2c72c 100644 --- a/crates/engine/src/files.rs +++ b/crates/engine/src/files.rs @@ -2296,4 +2296,25 @@ mod tests { assert_eq!(s.quick_view_job(id, 1600, true).unwrap().run().quick, Some(QuickSource::Small)); let _ = std::fs::remove_dir_all(&dir); } + + /// Issue #1448: the grid renders nothing behind an unedited raw's quick thumbnail, so when the + /// embedded preview can't be read (and nothing is cached) the quick job itself must render the + /// raw, or the cell stays empty for good ("no quick preview"). + #[test] + fn a_quick_thumbnail_without_an_embedded_preview_renders_the_raw() { + use crate::media::QuickSource; + let dir = std::env::temp_dir().join(format!("lc-quick-none-{}", std::process::id())); + std::fs::create_dir_all(&dir).unwrap(); + let path = dir.join("a.cr3"); + std::fs::write(&path, cr3_with_preview(96, 64)).unwrap(); + let mut s = crate::Session::new().with_fs(); + let r = s.execute("library.import", &serde_json::json!({"paths": [path.to_string_lossy()]})).unwrap(); + let id = lightcraft_catalog::PhotoId(r["imported"][0].as_u64().unwrap()); + s.media.preview_loader = Some(std::sync::Arc::new(|_: &str, _| None)); + let job = s.thumb_job(id, 128).unwrap(); + let q = s.quick_thumb_job(&job).unwrap().run(); + assert_eq!((q.quick, q.key), (Some(QuickSource::Small), job.key)); + assert!(q.rendered.is_ok(), "{:?}", q.rendered.err()); + let _ = std::fs::remove_dir_all(&dir); + } } diff --git a/crates/engine/src/media.rs b/crates/engine/src/media.rs index cf17c3264..e2417ec65 100644 --- a/crates/engine/src/media.rs +++ b/crates/engine/src/media.rs @@ -1427,8 +1427,10 @@ impl crate::Session { /// A first grid thumbnail for an unedited raw: the cached render for `job` (the real /// thumbnail job, from [`Self::thumb_job`]) if there is one — then the result carries - /// [`QuickSource::Cached`] and `job.key` and is final — else its embedded preview. `None` for - /// other photos (render them directly). + /// [`QuickSource::Cached`] and `job.key` and is final — else its embedded preview, else `job` + /// itself: the grid queues no render behind an unedited raw's quick thumbnail + /// ([`Self::embedded_thumb_is_final`]), so a preview that can't be read must not leave the + /// cell empty (issue #1448). `None` for other photos (render them directly). pub fn quick_thumb_job(&mut self, job: &RenderJob) -> Option { let p = self.catalog.photo(job.photo)?.clone(); let (path, l) = self.embedded_of(&p)?; @@ -1439,7 +1441,7 @@ impl crate::Session { key: job.key, cached: job.cache.clone().into_iter().collect(), embedded: Some((path, l, edge)), - small: None, + small: Some(Box::new(job.clone())), }) } diff --git a/docs/raw-preview-reads.md b/docs/raw-preview-reads.md index fe82f4662..30ebd642f 100644 --- a/docs/raw-preview-reads.md +++ b/docs/raw-preview-reads.md @@ -101,6 +101,9 @@ loupe render of a cold photo finished 174 s after the click, most of it spent be - `grid::skips_the_real_render(embedded_is_final, from_embedded)` is the decision, unit-tested: a **cached render** as the stand-in, an **edited photo** and a **raw with no embedded JPEG** all still render, because their thumbnail must show the pipeline's output rather than the camera's. +- Whether the embedded JPEG can actually be read is only known when the quick job runs, so + `Session::quick_thumb_job` carries the real thumbnail job as its last resort: a raw whose preview + the loader can't read renders the raw instead of leaving the cell empty (issue #1448). - The thumbnail key still changes with the settings hash, so editing a photo re-renders its thumbnail through the ordinary background path.