diff options
| author | cashmere <cashmere@autistici.org> | 2026-07-28 20:14:51 +0200 |
|---|---|---|
| committer | cashmere <cashmere@autistici.org> | 2026-07-28 20:14:51 +0200 |
| commit | 32438ce25f69de99d2523b98237f62cfa90890d7 (patch) | |
| tree | 3a712c347dec0889bdea3328b4c817c2d9392b27 /rust/crates/mask-defs/src/freeform.rs | |
| parent | f5a82152394fde4d8f740f664ab7737b82861618 (diff) | |
| download | opencut-32438ce25f69de99d2523b98237f62cfa90890d7.tar.gz | |
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)
Diffstat (limited to 'rust/crates/mask-defs/src/freeform.rs')
| -rw-r--r-- | rust/crates/mask-defs/src/freeform.rs | 87 |
1 files changed, 87 insertions, 0 deletions
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<Path> { + 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() +} |
