From 57069293fbee686be7ee4b9e0c40cdf94f89f2d1 Mon Sep 17 00:00:00 2001 From: AJ Depew Date: Wed, 7 Oct 2026 14:35:41 -0600 Subject: [PATCH] Graphics: New Layer from File anchors the picture at its own centre MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit graphics.newFromFile resolved the Motion anchor with the sequence frame size instead of the imported picture's size, so a picture smaller than the frame got its anchor at the frame centre (e.g. 960,540 for a 4x2 image). Setting Motion ▸ Position then moved the wrong point and the picture landed far from where it was asked to go. place_video_clip now resolves auto points with the source's own size (Project::source_size), falling back to the frame size. The new-layer-from-file test checks the anchor is the picture's centre; it fails before this change. Co-Authored-By: Claude Opus 5.5 --- crates/engine/src/graphics.rs | 4 +++- crates/engine/src/graphics_tests.rs | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/crates/engine/src/graphics.rs b/crates/engine/src/graphics.rs index b7e57625a..6a69369ac 100644 --- a/crates/engine/src/graphics.rs +++ b/crates/engine/src/graphics.rs @@ -124,13 +124,15 @@ pub(crate) fn place_video_clip( (q.settings.width, q.settings.height, q.settings.frame_rate) }; let (src, dur) = source(pr, (w, h, rate)); + // an imported picture (`graphics.newFromFile`) keeps its own size: centre its anchor in it + let src_size = pr.source_size(src).unwrap_or((w, h)); let t = rate.snap(t); let dur = dur.max(rate.frame_duration()); let mut ti = pr.make_track_item(src, TrackKind::Video, t, TimeRange::new(Tick::ZERO, dur), rate).ok_or_else(|| bad("graphics.newText", "bad item"))?; ti.name = name; ti.effects.extend(extra); for e in &mut ti.effects { - filmcraft_project::resolve_auto_points(e, (w, h), (w, h)); + filmcraft_project::resolve_auto_points(e, (w, h), src_size); } let id = ti.id; let track_id = filmcraft_project::TrackId(pr.alloc_id()); diff --git a/crates/engine/src/graphics_tests.rs b/crates/engine/src/graphics_tests.rs index 10ae78527..33fe00947 100644 --- a/crates/engine/src/graphics_tests.rs +++ b/crates/engine/src/graphics_tests.rs @@ -369,6 +369,9 @@ fn new_layer_from_file_places_the_image_above() { assert_eq!(it.start, q.settings.frame_rate.snap(t)); let ti = q.video_tracks.iter().position(|tr| tr.id == tid).unwrap(); assert!(q.video_tracks[..ti].iter().any(|tr| tr.item_at(t).is_some()), "above the footage"); + // the anchor is the picture's centre, not the frame's, so `position` places the picture + let anchor = it.effect("motion").unwrap().vec2_at("anchor", Tick::ZERO); + assert_eq!((anchor.x, anchor.y), (w as f64 / 2.0, h as f64 / 2.0)); assert_eq!(s.state.selection, vec![clip]); assert!(s.execute("graphics.newFromFile", json!({})).is_err()); let _ = std::fs::remove_dir_all(&dir);