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/graphics/tests/graphics_render.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/graphics/tests/graphics_render.rs')
| -rw-r--r-- | rust/crates/graphics/tests/graphics_render.rs | 261 |
1 files changed, 261 insertions, 0 deletions
diff --git a/rust/crates/graphics/tests/graphics_render.rs b/rust/crates/graphics/tests/graphics_render.rs new file mode 100644 index 00000000..a72f7fc9 --- /dev/null +++ b/rust/crates/graphics/tests/graphics_render.rs @@ -0,0 +1,261 @@ +use graphics::color::{hsl_to_rgb, is_transparent, parse_color_to_rgb, parse_css_color}; +use graphics::{ + GraphicsError, ParamValue, ParamValues, build_default_graphic_instance, draw_css_background, + get_graphic_definition, graphics_registry, parse_background_layers, render_graphic, + resolve_graphic_params, split_css_layers, BackgroundLayer, GradientKind, +}; +use tiny_skia::Pixmap; + +fn pixel(pixmap: &Pixmap, x: u32, y: u32) -> (u8, u8, u8, u8) { + let px = pixmap.pixel(x, y).unwrap(); + (px.red(), px.green(), px.blue(), px.alpha()) +} + +fn params(entries: &[(&str, ParamValue)]) -> ParamValues { + entries + .iter() + .map(|(key, value)| (key.to_string(), value.clone())) + .collect() +} + +#[test] +fn registry_lookup() { + let registry = graphics_registry(); + assert!(registry.has("rectangle")); + assert!(registry.has("ellipse")); + assert!(registry.has("polygon")); + assert!(registry.has("star")); + assert_eq!(registry.get_all().len(), 4); + + let rectangle = get_graphic_definition("rectangle").unwrap(); + assert_eq!(rectangle.name, "Rectangle"); + assert!(rectangle.keywords.contains(&"box")); + + let err = match get_graphic_definition("nope") { + Ok(_) => panic!("expected unknown graphic error"), + Err(err) => err, + }; + assert_eq!(err, GraphicsError::UnknownGraphic("nope".to_string())); + assert_eq!(err.to_string(), "Unknown graphic: nope"); +} + +#[test] +fn default_instance_and_resolve() { + let instance = build_default_graphic_instance("polygon").unwrap(); + assert_eq!(instance.definition_id, "polygon"); + assert_eq!(instance.params.get("sides"), Some(&ParamValue::Number(5.0))); + assert_eq!( + instance.params.get("fill"), + Some(&ParamValue::String("#ffffff".to_string())) + ); + assert_eq!( + instance.params.get("strokeAlign"), + Some(&ParamValue::String("center".to_string())) + ); + + let definition = get_graphic_definition("star").unwrap(); + let resolved = resolve_graphic_params( + definition, + Some(¶ms(&[("depth", ParamValue::Number(30.0))])), + ); + assert_eq!(resolved.get("depth"), Some(&ParamValue::Number(30.0))); + assert_eq!(resolved.get("points"), Some(&ParamValue::Number(5.0))); + + let defaults = resolve_graphic_params(definition, None); + assert_eq!(defaults.get("depth"), Some(&ParamValue::Number(45.0))); +} + +#[test] +fn color_parsing() { + let red = parse_css_color("#ff0000").unwrap(); + assert_eq!((red.r, red.g, red.b, red.a), (1.0, 0.0, 0.0, 1.0)); + let short = parse_css_color("#f00").unwrap(); + assert_eq!(short, red); + let with_alpha = parse_css_color("#ff000080").unwrap(); + assert!((with_alpha.a - 128.0 / 255.0).abs() < 1e-9); + let named = parse_css_color("rebeccapurple").unwrap(); + assert!((named.r - 102.0 / 255.0).abs() < 1e-9); + assert_eq!(parse_css_color("transparent").unwrap().a, 0.0); + let rgb = parse_css_color("rgb(255, 0, 0)").unwrap(); + assert_eq!(rgb, red); + let rgb_percent = parse_css_color("rgb(100%, 0%, 0%)").unwrap(); + assert!((rgb_percent.r - 1.0).abs() < 1e-9); + let rgba = parse_css_color("rgba(0, 255, 0, 0.5)").unwrap(); + assert_eq!((rgba.g, rgba.a), (1.0, 0.5)); + let hsl = parse_css_color("hsl(120, 100%, 50%)").unwrap(); + assert!((hsl.g - 1.0).abs() < 1e-9 && hsl.r.abs() < 1e-9); + assert!(parse_css_color("not-a-color").is_none()); + + let (r, g, b) = hsl_to_rgb(0.0, 1.0, 0.5); + assert_eq!((r, g, b), (1.0, 0.0, 0.0)); + + assert!(is_transparent("transparent")); + assert!(is_transparent("rgba(1, 2, 3, 0)")); + assert!(!is_transparent("rgba(1, 2, 3, 0.5)")); + assert!(!is_transparent("#00000000")); + + let parsed = parse_color_to_rgb("#ff0000").unwrap(); + assert_eq!((parsed.r, parsed.g, parsed.b), (255.0, 0.0, 0.0)); + assert!(parse_color_to_rgb("#ff00").is_none()); + assert!(parse_color_to_rgb("hsl(0,100%,50%)").is_none()); +} + +#[test] +fn layer_splitting() { + assert_eq!( + split_css_layers("#00ff00, linear-gradient(red, blue), rgba(1,2,3,0.5)"), + vec!["#00ff00", "linear-gradient(red, blue)", "rgba(1,2,3,0.5)"] + ); + + let layers = parse_background_layers("linear-gradient(red, blue)"); + assert!(matches!( + &layers[0], + BackgroundLayer::Gradient(ast) if ast.kind == GradientKind::Linear + )); + + let layers = parse_background_layers("rebeccapurple"); + assert_eq!(layers, vec![BackgroundLayer::Color("rebeccapurple".to_string())]); +} + +#[test] +fn rectangle_render_regions() { + let pixmap = render_graphic("rectangle", None, 64, 64).unwrap(); + assert_eq!(pixmap.width(), 64); + assert_eq!(pixmap.height(), 64); + assert_eq!(pixel(&pixmap, 32, 32), (255, 255, 255, 255)); + assert_eq!(pixel(&pixmap, 0, 0), (255, 255, 255, 255)); + + let rounded = render_graphic( + "rectangle", + Some(¶ms(&[("cornerRadius", ParamValue::Number(50.0))])), + 64, + 64, + ) + .unwrap(); + assert_eq!(pixel(&rounded, 32, 32), (255, 255, 255, 255)); + assert_eq!(pixel(&rounded, 0, 0), (0, 0, 0, 0)); +} + +#[test] +fn ellipse_render_regions() { + let pixmap = render_graphic("ellipse", None, 64, 64).unwrap(); + assert_eq!(pixel(&pixmap, 32, 32), (255, 255, 255, 255)); + assert_eq!(pixel(&pixmap, 0, 0), (0, 0, 0, 0)); + assert_eq!(pixel(&pixmap, 63, 63), (0, 0, 0, 0)); +} + +#[test] +fn star_render_regions() { + let pixmap = render_graphic("star", None, 64, 64).unwrap(); + assert_eq!(pixel(&pixmap, 32, 32), (255, 255, 255, 255)); + assert_eq!(pixel(&pixmap, 0, 0), (0, 0, 0, 0)); +} + +#[test] +fn polygon_render_regions() { + let pixmap = render_graphic( + "polygon", + Some(¶ms(&[("sides", ParamValue::Number(6.0))])), + 64, + 64, + ) + .unwrap(); + assert_eq!(pixel(&pixmap, 32, 32), (255, 255, 255, 255)); + assert_eq!(pixel(&pixmap, 0, 0), (0, 0, 0, 0)); +} + +#[test] +fn center_stroke_regions() { + let pixmap = render_graphic( + "rectangle", + Some(¶ms(&[ + ("strokeWidth", ParamValue::Number(10.0)), + ("strokeAlign", ParamValue::String("center".to_string())), + ])), + 64, + 64, + ) + .unwrap(); + assert_eq!(pixel(&pixmap, 2, 32), (0, 0, 0, 255)); + assert_eq!(pixel(&pixmap, 32, 32), (255, 255, 255, 255)); +} + +#[test] +fn inside_stroke_regions() { + let pixmap = render_graphic( + "rectangle", + Some(¶ms(&[ + ("strokeWidth", ParamValue::Number(10.0)), + ("strokeAlign", ParamValue::String("inside".to_string())), + ])), + 64, + 64, + ) + .unwrap(); + assert_eq!(pixel(&pixmap, 4, 32), (0, 0, 0, 255)); + assert_eq!(pixel(&pixmap, 32, 32), (255, 255, 255, 255)); +} + +#[test] +fn outside_stroke_has_no_inner_ring() { + let pixmap = render_graphic( + "rectangle", + Some(¶ms(&[ + ("strokeWidth", ParamValue::Number(10.0)), + ("strokeAlign", ParamValue::String("outside".to_string())), + ])), + 64, + 64, + ) + .unwrap(); + assert_eq!(pixel(&pixmap, 12, 32), (255, 255, 255, 255)); + assert_eq!(pixel(&pixmap, 32, 32), (255, 255, 255, 255)); +} + +#[test] +fn invalid_fill_color_errors() { + let err = render_graphic( + "rectangle", + Some(¶ms(&[("fill", ParamValue::String("bogus".to_string()))])), + 8, + 8, + ) + .unwrap_err(); + assert!(matches!(err, GraphicsError::InvalidColor(_))); +} + +#[test] +fn gradient_background_linear() { + let mut pixmap = Pixmap::new(64, 64).unwrap(); + draw_css_background(&mut pixmap, "linear-gradient(to right, #ff0000, #0000ff)").unwrap(); + let left = pixel(&pixmap, 1, 32); + let right = pixel(&pixmap, 62, 32); + assert!(left.0 > 200 && left.2 < 60, "left was {left:?}"); + assert!(right.2 > 200 && right.0 < 60, "right was {right:?}"); + assert_eq!(left.3, 255); + assert_eq!(right.3, 255); +} + +#[test] +fn gradient_background_radial() { + let mut pixmap = Pixmap::new(64, 64).unwrap(); + draw_css_background(&mut pixmap, "radial-gradient(circle, #ffffff, #000000)").unwrap(); + let center = pixel(&pixmap, 32, 32); + assert!(center.0 > 200, "center was {center:?}"); + let corner = pixel(&pixmap, 0, 0); + assert!(corner.0 < 60, "corner was {corner:?}"); +} + +#[test] +fn background_layer_paint_order() { + let mut pixmap = Pixmap::new(16, 16).unwrap(); + draw_css_background(&mut pixmap, "#00ff00, linear-gradient(#ff0000, #0000ff)").unwrap(); + assert_eq!(pixel(&pixmap, 8, 8), (0, 255, 0, 255)); + + let mut pixmap = Pixmap::new(16, 16).unwrap(); + draw_css_background(&mut pixmap, "rgba(255,0,0,0.5)").unwrap(); + let px = pixel(&pixmap, 8, 8); + assert!(px.3 > 120 && px.3 < 135, "alpha was {px:?}"); + assert!(px.0 > 120 && px.0 < 135, "premultiplied red was {px:?}"); + assert_eq!(px.1, 0); +} |
