mirror of
https://github.com/storytold/filmcraft.git
synced 2026-10-14 19:26:59 -04:00
masks.addVertex: clamp oversized index on animated paths, reject empty paths instead of panicking (#543)
This commit is contained in:
1 parent
7bd7621262
commit
b40964db91
2 files changed
+51
-7
No files matched your search
@@ -366,23 +366,34 @@ fn add_vertex(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let after = u64_p(p, "after").ok_or_else(|| bad("masks.addVertex", "need `after`"))? as usize;
|
||||
let at = p.get("at").and_then(pt).ok_or_else(|| bad("masks.addVertex", "need `at`"))?;
|
||||
edit_mask(s, p, "Add Mask Vertex", move |m, t| {
|
||||
let n = m.path_at(t).vertices.len();
|
||||
// clamp against the length before any insertion, so the index stays valid in the grown path
|
||||
let i = (after + 1).min(n);
|
||||
if m.path.is_animated() && m.path.keyframes.len() > 1 {
|
||||
if n == 0 {
|
||||
return Err(bad("masks.addVertex", "the animated mask path has no vertices to insert after"));
|
||||
}
|
||||
// keep every keyframe interpolable: insert the vertex into all of them
|
||||
for k in &mut m.path.keyframes {
|
||||
if let ParamValue::Path(path) = &mut k.value {
|
||||
let i = (after + 1).min(path.vertices.len());
|
||||
let a = path.vertices[after.min(path.vertices.len() - 1)].p;
|
||||
let b = path.vertices[i % path.vertices.len()].p;
|
||||
path.vertices.insert(i, MaskVertex::corner(a.lerp(b, 0.5)));
|
||||
let len = path.vertices.len();
|
||||
if len == 0 {
|
||||
continue;
|
||||
}
|
||||
let at_i = i.min(len);
|
||||
let a = path.vertices[after.min(len - 1)].p;
|
||||
let b = path.vertices[at_i % len].p;
|
||||
path.vertices.insert(at_i, MaskVertex::corner(a.lerp(b, 0.5)));
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut path = m.path_at(t);
|
||||
let i = (after + 1).min(path.vertices.len());
|
||||
if m.path.keyframes.len() > 1 {
|
||||
path.vertices[i] = MaskVertex::corner(at);
|
||||
if let Some(v) = path.vertices.get_mut(i) {
|
||||
*v = MaskVertex::corner(at);
|
||||
}
|
||||
} else {
|
||||
path.vertices.insert(i, MaskVertex::corner(at));
|
||||
path.vertices.insert(i.min(path.vertices.len()), MaskVertex::corner(at));
|
||||
}
|
||||
set_path_at(m, t, path);
|
||||
Ok(())
|
||||
|
||||
@@ -106,6 +106,39 @@ fn keyframed_mask_path_interpolates() {
|
||||
assert_eq!(a["masks"][0]["path"]["vertices"].as_array().unwrap().len(), 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_vertex_on_animated_path_clamps_oversized_index_and_rejects_empty_path() {
|
||||
let (mut s, clip) = demo();
|
||||
let it = s.active_sequence().unwrap().find_item(clip).unwrap().1.clone();
|
||||
let rate = s.active_sequence().unwrap().settings.frame_rate;
|
||||
s.set_playhead(it.start);
|
||||
s.execute("masks.add", json!({"effect": "opacity", "shape": "polygon", "center": [400, 400], "size": [200, 200]})).unwrap();
|
||||
s.execute("effects.toggleAnimation", json!({"clip": clip.0, "effect": "opacity", "mask": 0, "param": "path"})).unwrap();
|
||||
s.set_playhead(it.start + rate.tick_of(10));
|
||||
s.execute("masks.translate", json!({"delta": [5, 0]})).unwrap();
|
||||
// an index past the last vertex appends instead of panicking
|
||||
s.execute("masks.addVertex", json!({"after": 100, "at": [1, 1]})).unwrap();
|
||||
let l = s.execute("masks.list", json!({})).unwrap();
|
||||
assert_eq!(l["masks"][0]["path"]["vertices"].as_array().unwrap().len(), 5);
|
||||
|
||||
// an empty animated path is a parameter error and leaves the mask alone
|
||||
s.set_playhead(it.start);
|
||||
s.execute("masks.add", json!({"effect": "opacity", "path": {"vertices": [], "closed": false}})).unwrap();
|
||||
s.execute("effects.toggleAnimation", json!({"clip": clip.0, "effect": "opacity", "mask": 1, "param": "path"})).unwrap();
|
||||
s.set_playhead(it.start + rate.tick_of(10));
|
||||
s.execute("masks.translate", json!({"mask": 1, "delta": [5, 0]})).unwrap();
|
||||
let e = s.execute("masks.addVertex", json!({"mask": 1, "after": 0, "at": [1, 1]})).unwrap_err().to_string();
|
||||
assert!(e.contains("no vertices"), "{e}");
|
||||
let l = s.execute("masks.list", json!({})).unwrap();
|
||||
assert_eq!(l["masks"][1]["path"]["vertices"].as_array().unwrap().len(), 0);
|
||||
|
||||
// a static empty path still takes its first vertex
|
||||
s.execute("masks.add", json!({"effect": "opacity", "path": {"vertices": [], "closed": false}})).unwrap();
|
||||
s.execute("masks.addVertex", json!({"mask": 2, "after": 0, "at": [1, 1]})).unwrap();
|
||||
let l = s.execute("masks.list", json!({})).unwrap();
|
||||
assert_eq!(l["masks"][2]["path"]["vertices"].as_array().unwrap().len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn merged_drag_is_one_undo_step_and_project_round_trips() {
|
||||
let (mut s, _) = demo();
|
||||
|
||||
Reference in new issue
Block a user