G5: a still decoded larger than its nominal size is resampled like the renderer does (#1812)

Review: still_at_size returned a smart object's pixels unresampled when they differed from the
footage's nominal size. It now follows footage_buf: pixels larger than nominal are resampled to
the requested size, smaller ones are left for the renderer to scale up.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ded
2026-10-20 04:21:14 +03:00
co-authored by Claude Opus 5.5
parent c33d5c20df
commit ea1f2fe984
2 changed files with 16 additions and 5 deletions
+8 -5
View File
@@ -774,13 +774,16 @@ impl Inner {
#[cfg(target_arch = "wasm32")]
fn prefetch(self: &Arc<Self>, _footage: &Footage, _t: Tick, _size: Option<(u32, u32)>, _float: FloatCurve) {}
/// A still (image file or sequence frame) of the footage's own size, at `loc.key.size` when
/// one is set: resampled as the renderer would ([`FootageSource::frame_at_size`]), so the
/// pool keeps the reduced frame and a reduced-resolution render neither decodes nor resamples
/// a large still again for every frame (#1812: 21120-px plates at Quarter).
/// A still (image file or sequence frame) at `loc.key.size` when one is set: resampled as the
/// renderer would ([`FootageSource::frame_at_size`]), so the pool keeps the reduced frame and a
/// reduced-resolution render neither decodes nor resamples a large still again for every frame
/// (#1812: 21120-px plates at Quarter). Like the renderer's `footage_buf`, an image decoded
/// larger than the footage's nominal size (a smart object's pixels) is resampled too, and one
/// decoded smaller is not (the renderer scales the layer instead).
fn still_at_size(img: Image, loc: &Loc, footage: &Footage) -> Image {
let (w, h) = loc.key.size;
if w == 0 || h == 0 || (img.width, img.height) != (footage.width, footage.height) || (w, h) == (img.width, img.height) {
let smaller = img.width == 0 || footage.width as f64 / img.width as f64 > 1.0 + 1e-9;
if w == 0 || h == 0 || smaller || (w, h) == (img.width, img.height) {
return img;
}
effectcraft_raster::resample(&img, w, h)
+8
View File
@@ -608,6 +608,14 @@ fn stills_at_reduced_sizes_are_resampled_once() {
assert!(Arc::ptr_eq(&sized, &again), "{w}×{h}: kept at that size");
assert_eq!(pool.stats().misses, misses, "{w}×{h}: not decoded again");
}
// Pixels larger than the nominal size (a smart object's) are resampled as the renderer does;
// smaller ones are left as they are (the renderer scales the layer up instead).
let larger = Footage { width: 306, height: 48, ..f.clone() };
let sized = MediaPool::new().frame_at_size(ItemId(1), &larger, Tick::ZERO, 153, 24).expect("frame");
assert_eq!(*sized, effectcraft_raster::resample(&full, 153, 24));
let smaller = Footage { width: 1226, height: 194, ..f.clone() };
let kept = MediaPool::new().frame_at_size(ItemId(1), &smaller, Tick::ZERO, 306, 48).expect("frame");
assert_eq!(*kept, *full);
let _ = std::fs::remove_dir_all(&dir);
}