| mod test_env; |
| |
| #[cfg(test)] |
| mod tests { |
| use super::test_env::*; |
| use forma::{ |
| BlendMode, Color, Fill, FillRule, Func, Gradient, GradientBuilder, Order, Path, |
| PathBuilder, Point, Props, Style, |
| }; |
| |
| fn triangle() -> Path { |
| PathBuilder::new() |
| .move_to(Point { x: PADDING, y: PADDING }) |
| .line_to(Point { x: WIDTH - PADDING, y: PADDING }) |
| .line_to(Point { x: WIDTH - PADDING, y: HEIGHT - PADDING }) |
| .build() |
| } |
| |
| fn square() -> Path { |
| PathBuilder::new() |
| .move_to(Point { x: PADDING, y: PADDING }) |
| .line_to(Point { x: WIDTH - PADDING, y: PADDING }) |
| .line_to(Point { x: WIDTH - PADDING, y: HEIGHT - PADDING }) |
| .line_to(Point { x: PADDING, y: HEIGHT - PADDING }) |
| .build() |
| } |
| |
| fn inner_square() -> Path { |
| PathBuilder::new() |
| .move_to(Point { x: PADDING * 2.0, y: PADDING * 2.0 }) |
| .line_to(Point { x: WIDTH - PADDING * 2.0, y: PADDING * 2.0 }) |
| .line_to(Point { x: WIDTH - PADDING * 2.0, y: HEIGHT - PADDING * 2.0 }) |
| .line_to(Point { x: PADDING * 2.0, y: HEIGHT - PADDING * 2.0 }) |
| .build() |
| } |
| |
| fn circle() -> Path { |
| let weight = 2.0f32.sqrt() / 2.0; |
| let x = WIDTH * 0.5; |
| let y = HEIGHT * 0.5; |
| let radius = WIDTH * 0.5 - PADDING; |
| PathBuilder::new() |
| .move_to(Point::new(x + radius, y)) |
| .rat_quad_to(Point::new(x + radius, y - radius), Point::new(x, y - radius), weight) |
| .rat_quad_to(Point::new(x - radius, y - radius), Point::new(x - radius, y), weight) |
| .rat_quad_to(Point::new(x - radius, y + radius), Point::new(x, y + radius), weight) |
| .rat_quad_to(Point::new(x + radius, y + radius), Point::new(x + radius, y), weight) |
| .build() |
| } |
| |
| fn rainbow_colors(gradient_builder: &mut GradientBuilder) { |
| gradient_builder |
| .color(Color { r: 1.00, g: 0.00, b: 0.00, a: 1.0 }) |
| .color(Color { r: 1.00, g: 0.32, b: 0.00, a: 1.0 }) |
| .color(Color { r: 0.63, g: 0.73, b: 0.02, a: 1.0 }) |
| .color(Color { r: 0.08, g: 0.72, b: 0.07, a: 1.0 }) |
| .color(Color { r: 0.05, g: 0.70, b: 0.69, a: 1.0 }) |
| .color(Color { r: 0.03, g: 0.58, b: 0.76, a: 1.0 }) |
| .color(Color { r: 0.01, g: 0.21, b: 0.85, a: 1.0 }) |
| .color(Color { r: 0.11, g: 0.01, b: 0.89, a: 1.0 }) |
| .color(Color { r: 0.49, g: 0.00, b: 0.94, a: 1.0 }) |
| .color(Color { r: 0.96, g: 0.00, b: 0.69, a: 1.0 }) |
| .color(Color { r: 1.00, g: 0.00, b: 0.00, a: 1.0 }); |
| } |
| |
| fn vertical_rainbow() -> Gradient { |
| let mut gradient_builder = GradientBuilder::new( |
| Point { x: PADDING, y: 0.0 }, |
| Point { x: WIDTH - PADDING, y: 0.0 }, |
| ); |
| rainbow_colors(&mut gradient_builder); |
| gradient_builder.build().unwrap() |
| } |
| |
| fn horizontal_rainbow() -> Gradient { |
| let mut gradient_builder = GradientBuilder::new( |
| Point { x: 0.0, y: PADDING }, |
| Point { x: 0.0, y: WIDTH - PADDING }, |
| ); |
| rainbow_colors(&mut gradient_builder); |
| gradient_builder.build().unwrap() |
| } |
| |
| fn solid_color_props(color: Color) -> Props { |
| Props { |
| func: Func::Draw(Style { fill: Fill::Solid(color), ..Default::default() }), |
| ..Default::default() |
| } |
| } |
| |
| #[test] |
| fn linear_gradient() { |
| let test_env = test_env!(); |
| test_env.test_render(|composition| { |
| let mut gradient_builder = GradientBuilder::new( |
| Point { x: PADDING, y: 0.0 }, |
| Point { x: WIDTH - PADDING, y: 0.0 }, |
| ); |
| gradient_builder |
| .color(Color { r: 0.0, g: 0.0, b: 1.0, a: 1.0 }) |
| .color(Color { r: 1.0, g: 1.0, b: 1.0, a: 1.0 }) |
| .color(Color { r: 1.0, g: 0.0, b: 0.0, a: 1.0 }); |
| let props = Props { |
| func: Func::Draw(Style { |
| fill: Fill::Gradient(gradient_builder.build().unwrap()), |
| ..Default::default() |
| }), |
| ..Default::default() |
| }; |
| composition |
| .get_mut_or_insert_default(Order::new(1).unwrap()) |
| .insert(&triangle()) |
| .set_props(props); |
| }); |
| } |
| |
| #[test] |
| fn radial_gradient() { |
| let test_env = test_env!(); |
| test_env.test_render(|composition| { |
| let mut gradient_builder = GradientBuilder::new( |
| Point { x: WIDTH * 0.5, y: HEIGHT * 0.5 }, |
| Point { x: WIDTH - PADDING * 2.0, y: HEIGHT * 0.5 }, |
| ); |
| gradient_builder |
| .r#type(forma::GradientType::Radial) |
| .color(Color { r: 0.0, g: 0.0, b: 1.0, a: 1.0 }) |
| .color(Color { r: 1.0, g: 1.0, b: 1.0, a: 1.0 }) |
| .color(Color { r: 1.0, g: 0.0, b: 0.0, a: 1.0 }); |
| let props = Props { |
| func: Func::Draw(Style { |
| fill: Fill::Gradient(gradient_builder.build().unwrap()), |
| ..Default::default() |
| }), |
| ..Default::default() |
| }; |
| composition |
| .get_mut_or_insert_default(Order::new(1).unwrap()) |
| .insert(&circle()) |
| .set_props(props); |
| }); |
| } |
| |
| #[test] |
| fn solid_color() { |
| let test_env = test_env!(); |
| let colors = vec![ |
| (Color { r: 0.0, g: 0.0, b: 1.0, a: 1.0 }, "blue"), |
| (Color { r: 0.0, g: 0.0, b: 0.5, a: 1.0 }, "dark_blue"), |
| (Color { r: 1.0, g: 0.0, b: 0.0, a: 1.0 }, "red"), |
| (Color { r: 0.5, g: 0.0, b: 0.0, a: 1.0 }, "dark_red"), |
| (Color { r: 0.0, g: 1.0, b: 0.0, a: 1.0 }, "green"), |
| (Color { r: 0.0, g: 0.5, b: 0.0, a: 1.0 }, "dark_green"), |
| (Color { r: 0.0, g: 0.0, b: 0.0, a: 0.5 }, "transparent_black"), |
| ]; |
| for (color, name) in colors { |
| test_env.test_render_param( |
| |composition| { |
| composition |
| .get_mut_or_insert_default(Order::new(1).unwrap()) |
| .insert(&square()) |
| .set_props(solid_color_props(color)); |
| }, |
| name, |
| ); |
| } |
| } |
| |
| #[test] |
| fn pixel() { |
| // This test is useful when the reasterizer is brocken as it emmits 2 pixel segments. |
| test_env!().test_render(|composition| { |
| composition |
| .get_mut_or_insert_default(Order::new(1).unwrap()) |
| .insert( |
| &PathBuilder::new() |
| .move_to(Point { x: PADDING, y: PADDING }) |
| .line_to(Point { x: PADDING + 1.0, y: PADDING }) |
| .line_to(Point { x: PADDING + 1.0, y: PADDING + 1.0 }) |
| .line_to(Point { x: PADDING, y: PADDING + 1.0 }) |
| .build(), |
| ) |
| .set_props(solid_color_props(Color { r: 0.0, g: 0.0, b: 0.0, a: 1.0 })); |
| }); |
| } |
| |
| #[test] |
| fn blend_modes() { |
| let test_env = test_env!(); |
| let blend_modes = [ |
| BlendMode::Over, |
| BlendMode::Multiply, |
| BlendMode::Screen, |
| BlendMode::Overlay, |
| BlendMode::Darken, |
| BlendMode::Lighten, |
| BlendMode::ColorDodge, |
| BlendMode::ColorBurn, |
| BlendMode::HardLight, |
| BlendMode::SoftLight, |
| BlendMode::Difference, |
| BlendMode::Exclusion, |
| BlendMode::Hue, |
| BlendMode::Saturation, |
| BlendMode::Color, |
| BlendMode::Luminosity, |
| ]; |
| for blend_mode in blend_modes { |
| test_env.test_render_param( |
| |composition| { |
| composition |
| .get_mut_or_insert_default(Order::new(0).unwrap()) |
| .insert(&square()) |
| .set_props(Props { |
| func: Func::Draw(Style { |
| fill: Fill::Gradient(horizontal_rainbow()), |
| ..Default::default() |
| }), |
| ..Default::default() |
| }); |
| |
| composition |
| .get_mut_or_insert_default(Order::new(1).unwrap()) |
| .insert(&triangle()) |
| .set_props(Props { |
| func: Func::Draw(Style { |
| fill: Fill::Gradient(vertical_rainbow()), |
| blend_mode, |
| ..Default::default() |
| }), |
| ..Default::default() |
| }); |
| }, |
| blend_mode, |
| ); |
| } |
| } |
| |
| #[test] |
| fn fill_rules() { |
| let test_env = test_env!(); |
| let fill_rules = [FillRule::EvenOdd, FillRule::NonZero]; |
| for fill_rule in fill_rules { |
| test_env.test_render_param( |
| |composition| { |
| let path = PathBuilder::new() |
| .move_to(Point { x: PADDING, y: PADDING }) |
| .line_to(Point { x: WIDTH / 2.0 + PADDING, y: HEIGHT / 2.0 + PADDING }) |
| .line_to(Point { x: WIDTH / 2.0 - PADDING, y: HEIGHT / 2.0 + PADDING }) |
| .line_to(Point { x: WIDTH - PADDING, y: PADDING }) |
| .line_to(Point { x: WIDTH - PADDING, y: HEIGHT - PADDING }) |
| .line_to(Point { x: PADDING, y: HEIGHT - PADDING }) |
| .build(); |
| composition |
| .get_mut_or_insert_default(Order::new(0).unwrap()) |
| .insert(&path) |
| .set_props(Props { |
| fill_rule, |
| func: Func::Draw(Style { |
| fill: Fill::Solid(Color { r: 0.0, g: 0.0, b: 0.0, a: 0.8 }), |
| ..Default::default() |
| }), |
| }); |
| }, |
| fill_rule, |
| ); |
| } |
| } |
| |
| #[test] |
| fn clipping() { |
| let test_env = test_env!(); |
| test_env.test_render(|composition| { |
| // First layer is not clipped. |
| composition |
| .get_mut_or_insert_default(Order::new(0).unwrap()) |
| .insert(&square()) |
| .set_props(solid_color_props(Color { r: 0.0, g: 0.0, b: 0.0, a: 0.2 })); |
| |
| // Triangular clip shape applies to the next 3 layers ids. |
| let props = Props { func: Func::Clip(3), ..Default::default() }; |
| composition |
| .get_mut_or_insert_default(Order::new(1).unwrap()) |
| .insert(&triangle()) |
| .set_props(props); |
| |
| // The blue square is clipped. |
| composition |
| .get_mut_or_insert_default(Order::new(2).unwrap()) |
| .insert(&square()) |
| .set_props(Props { |
| func: Func::Draw(Style { |
| fill: Fill::Solid(Color { r: 0.5, g: 0.5, b: 1.0, a: 0.6 }), |
| is_clipped: true, |
| ..Default::default() |
| }), |
| ..Default::default() |
| }); |
| |
| // Order No. 3 is intentionnaly left empty to test the clip implementation. |
| |
| // The pink circle is clipped. |
| composition |
| .get_mut_or_insert_default(Order::new(4).unwrap()) |
| .insert(&circle()) |
| .set_props(Props { |
| func: Func::Draw(Style { |
| fill: Fill::Solid(Color { r: 1.0, g: 0.5, b: 0.5, a: 0.6 }), |
| is_clipped: true, |
| ..Default::default() |
| }), |
| ..Default::default() |
| }); |
| |
| // This is not drawn given that `is_clipped: true` and no clipping |
| // is active at order 5. |
| composition |
| .get_mut_or_insert_default(Order::new(5).unwrap()) |
| .insert(&inner_square()) |
| .set_props(Props { |
| func: Func::Draw(Style { |
| fill: Fill::Solid(Color { r: 0.5, g: 0.5, b: 1.0, a: 0.6 }), |
| is_clipped: true, |
| ..Default::default() |
| }), |
| ..Default::default() |
| }); |
| }); |
| } |
| } |