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/lib.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/lib.rs')
| -rw-r--r-- | rust/crates/mask-defs/src/lib.rs | 191 |
1 files changed, 191 insertions, 0 deletions
diff --git a/rust/crates/mask-defs/src/lib.rs b/rust/crates/mask-defs/src/lib.rs index 8b137891..c86f5bdf 100644 --- a/rust/crates/mask-defs/src/lib.rs +++ b/rust/crates/mask-defs/src/lib.rs @@ -1 +1,192 @@ +mod freeform; +mod geometry; +mod paint; +mod shapes; +mod split; +mod text_mask; +pub use freeform::build_freeform_path; + +use scene::Mask; +use tiny_skia::Pixmap; + +#[derive(Debug, thiserror::Error)] +pub enum MaskRenderError { + #[error("invalid pixmap size {width}x{height}")] + InvalidSize { width: u32, height: u32 }, + #[error("invalid stroke color: {0}")] + InvalidStrokeColor(String), +} + +fn new_pixmap(width: u32, height: u32) -> Result<Pixmap, MaskRenderError> { + Pixmap::new(width, height).ok_or(MaskRenderError::InvalidSize { width, height }) +} + +pub fn is_active(mask: &Mask) -> bool { + match mask { + Mask::Text { params, .. } => !params.content.trim().is_empty(), + Mask::Freeform { params, .. } => params.closed, + _ => true, + } +} + +pub fn feather_for_gpu(mask: &Mask) -> f32 { + match mask { + Mask::Split { .. } => 0.0, + Mask::CinematicBars { params, .. } + | Mask::Rectangle { params, .. } + | Mask::Ellipse { params, .. } + | Mask::Heart { params, .. } + | Mask::Diamond { params, .. } + | Mask::Star { params, .. } => params.feather as f32, + Mask::Text { params, .. } => params.feather as f32, + Mask::Freeform { params, .. } => params.feather as f32, + } +} + +pub fn render_mask_body( + mask: &Mask, + width: u32, + height: u32, +) -> Result<Pixmap, MaskRenderError> { + let mut pixmap = new_pixmap(width, height)?; + let w = width as f64; + let h = height as f64; + match mask { + Mask::Split { params, .. } => { + if params.feather == 0.0 { + paint::fill_white(&split::split_opaque_body_path(params, w, h), &mut pixmap); + } else { + split::fill_split_feather_body(params, w, h, &mut pixmap); + } + } + Mask::CinematicBars { params, .. } => { + paint::fill_white(&shapes::cinematic_bars_body_path(params, w, h), &mut pixmap); + } + Mask::Rectangle { params, .. } => { + paint::fill_white(&shapes::rectangle_body_path(params, w, h), &mut pixmap); + } + Mask::Ellipse { params, .. } => { + paint::fill_white(&shapes::ellipse_body_path(params, w, h), &mut pixmap); + } + Mask::Heart { params, .. } => { + paint::fill_white(&shapes::heart_body_path(params, w, h), &mut pixmap); + } + Mask::Diamond { params, .. } => { + paint::fill_white(&shapes::diamond_body_path(params, w, h), &mut pixmap); + } + Mask::Star { params, .. } => { + paint::fill_white(&shapes::star_body_path(params, w, h), &mut pixmap); + } + Mask::Text { params, .. } => { + text_mask::render_body(params, w, h, &mut pixmap); + } + Mask::Freeform { params, .. } => { + if params.closed { + let path = build_freeform_path( + ¶ms.path, + params.center_x, + params.center_y, + params.rotation, + params.scale, + w, + h, + true, + ); + paint::fill_white(&path, &mut pixmap); + } + } + } + Ok(pixmap) +} + +pub fn render_mask_stroke( + mask: &Mask, + width: u32, + height: u32, +) -> Result<Option<Pixmap>, MaskRenderError> { + let (stroke_width, stroke_color) = match mask { + Mask::Split { params, .. } => (params.stroke_width, ¶ms.stroke_color), + Mask::CinematicBars { params, .. } + | Mask::Rectangle { params, .. } + | Mask::Ellipse { params, .. } + | Mask::Heart { params, .. } + | Mask::Diamond { params, .. } + | Mask::Star { params, .. } => (params.stroke_width, ¶ms.stroke_color), + Mask::Text { params, .. } => (params.stroke_width, ¶ms.stroke_color), + Mask::Freeform { params, .. } => (params.stroke_width, ¶ms.stroke_color), + }; + if stroke_width <= 0.0 { + return Ok(None); + } + + let mut pixmap = new_pixmap(width, height)?; + let w = width as f64; + let h = height as f64; + match mask { + Mask::Split { params, .. } => { + let color = paint::parse_stroke_color(stroke_color)?; + paint::stroke_path_with( + &split::split_stroke_path(params, w, h), + &paint::canvas_stroke(stroke_width), + color, + &mut pixmap, + ); + } + Mask::CinematicBars { .. } + | Mask::Rectangle { .. } + | Mask::Ellipse { .. } + | Mask::Heart { .. } + | Mask::Diamond { .. } + | Mask::Star { .. } => { + let path = match mask { + Mask::CinematicBars { params, .. } => { + shapes::cinematic_bars_stroke_path(params, w, h) + } + Mask::Rectangle { params, .. } => shapes::rectangle_stroke_path(params, w, h), + Mask::Ellipse { params, .. } => shapes::ellipse_stroke_path(params, w, h), + Mask::Heart { params, .. } => shapes::heart_stroke_path(params, w, h), + Mask::Diamond { params, .. } => shapes::diamond_stroke_path(params, w, h), + Mask::Star { params, .. } => shapes::star_stroke_path(params, w, h), + _ => unreachable!(), + }; + let color = paint::parse_stroke_color(stroke_color)?; + paint::stroke_path_with( + &path, + &paint::canvas_stroke(stroke_width), + color, + &mut pixmap, + ); + } + Mask::Text { params, .. } => { + text_mask::render_stroke(params, w, h, &mut pixmap)?; + } + Mask::Freeform { params, .. } => { + if params.closed { + let path = build_freeform_path( + ¶ms.path, + params.center_x, + params.center_y, + params.rotation, + params.scale, + w, + h, + true, + ); + let color = paint::parse_stroke_color(stroke_color)?; + paint::stroke_path_with( + &path, + &paint::round_stroke(stroke_width), + color, + &mut pixmap, + ); + if params.stroke_align != scene::StrokeAlign::Center { + let mut fill = new_pixmap(width, height)?; + paint::fill_white(&path, &mut fill); + paint::apply_stroke_align(&mut pixmap, &fill, params.stroke_align); + } + } + } + } + Ok(Some(pixmap)) +} |
