From 32438ce25f69de99d2523b98237f62cfa90890d7 Mon Sep 17 00:00:00 2001 From: cashmere Date: Tue, 28 Jul 2026 20:14:51 +0200 Subject: feat: port domain crates to Rust (session 1 complete) All green: cargo test --workspace (~300 tests, 0 failures). - timeline (45): placement, resize/ripple, split, snap, retime, audio separation, scene helpers - animation (73): bezier solver, channel sampling, keyframe ops, split/clamp/clone, param definitions - commands (9): Command trait, BatchCommand, history, 19 concrete commands, freeze-frame port 1:1 - audio (22): audible collection, range mixing, mastering limiter, rms/peak buckets - text (19): cosmic-text layout/measure/raster, backgrounds, decorations - graphics (52): full CSS gradient parser, tiny-skia raster, 4 shape definitions - effect-defs (16): blur definition, registry, pass resolution - mask-defs (19): all 9 mask renderers via tiny-skia - renderer (6): node model + scene builder port - desktop: GPUI projects screen wired to ProjectStore - scene: factory (web defaults: v32, Main Track, 1920x1080, 30fps) --- rust/crates/mask-defs/src/freeform.rs | 87 +++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 rust/crates/mask-defs/src/freeform.rs (limited to 'rust/crates/mask-defs/src/freeform.rs') diff --git a/rust/crates/mask-defs/src/freeform.rs b/rust/crates/mask-defs/src/freeform.rs new file mode 100644 index 00000000..6c464977 --- /dev/null +++ b/rust/crates/mask-defs/src/freeform.rs @@ -0,0 +1,87 @@ +use scene::FreeformPathPoint; +use tiny_skia::{Path, PathBuilder}; + +fn local_to_canvas( + x: f64, + y: f64, + center_x: f64, + center_y: f64, + rotation: f64, + scale: f64, + width: f64, + height: f64, +) -> (f64, f64) { + let canvas_center_x = width / 2.0 + center_x * width; + let canvas_center_y = height / 2.0 + center_y * height; + let scaled_x = x * width * scale; + let scaled_y = y * height * scale; + let angle_rad = rotation.to_radians(); + let cos = angle_rad.cos(); + let sin = angle_rad.sin(); + ( + canvas_center_x + scaled_x * cos - scaled_y * sin, + canvas_center_y + scaled_x * sin + scaled_y * cos, + ) +} + +#[allow(clippy::too_many_arguments)] +pub fn build_freeform_path( + points: &[FreeformPathPoint], + center_x: f64, + center_y: f64, + rotation: f64, + scale: f64, + width: f64, + height: f64, + closed: bool, +) -> Option { + if points.is_empty() { + return None; + } + + let to_canvas = |x: f64, y: f64| { + local_to_canvas(x, y, center_x, center_y, rotation, scale, width, height) + }; + + let anchors: Vec<((f64, f64), (f64, f64), (f64, f64))> = points + .iter() + .map(|point| { + ( + to_canvas(point.x, point.y), + to_canvas(point.x + point.in_x, point.y + point.in_y), + to_canvas(point.x + point.out_x, point.y + point.out_y), + ) + }) + .collect(); + + let mut builder = PathBuilder::new(); + builder.move_to(anchors[0].0 .0 as f32, anchors[0].0 .1 as f32); + for index in 1..anchors.len() { + let previous = anchors[index - 1]; + let current = anchors[index]; + builder.cubic_to( + previous.2 .0 as f32, + previous.2 .1 as f32, + current.1 .0 as f32, + current.1 .1 as f32, + current.0 .0 as f32, + current.0 .1 as f32, + ); + } + + if closed && anchors.len() > 1 { + let last = anchors[anchors.len() - 1]; + let first = anchors[0]; + builder.cubic_to( + last.2 .0 as f32, + last.2 .1 as f32, + first.1 .0 as f32, + first.1 .1 as f32, + first.0 .0 as f32, + first.0 .1 as f32, + ); + builder.close(); + } + + builder.finish() +} -- cgit v1.3.1