| // Copyright 2025 the Vello Authors | |
| // SPDX-License-Identifier: Apache-2.0 OR MIT | |
| //! Tests for clipping. | |
| use crate::renderer::Renderer; | |
| use crate::util::{circular_star, crossed_line_star, stops_green_blue}; | |
| use std::f64::consts::PI; | |
| use vello_common::color::palette::css::{ | |
| BLACK, BLUE, DARK_BLUE, DARK_GREEN, GREEN, REBECCA_PURPLE, RED, | |
| }; | |
| use vello_common::kurbo::{Affine, BezPath, Circle, Point, Rect, Shape, Stroke}; | |
| use vello_common::peniko::Color; | |
| use vello_common::peniko::Fill; | |
| use vello_common::tile::Tile; | |
| use vello_cpu::peniko::{ | |
| Gradient, LinearGradientPosition, RadialGradientPosition, SweepGradientPosition, | |
| }; | |
| use vello_dev_macros::vello_test; | |
| #[vello_test(gpu_tolerance = 1)] | |
| fn clip_triangle_with_star(ctx: &mut impl Renderer) { | |
| let mut triangle_path = BezPath::new(); | |
| triangle_path.move_to((10.0, 10.0)); | |
| triangle_path.line_to((90.0, 20.0)); | |
| triangle_path.line_to((20.0, 90.0)); | |
| triangle_path.close_path(); | |
| let stroke = Stroke::new(1.0); | |
| ctx.set_paint(DARK_BLUE); | |
| ctx.set_stroke(stroke); | |
| ctx.stroke_path(&triangle_path); | |
| let star_path = circular_star(Point::new(50., 50.), 13, 25., 45.); | |
| ctx.push_clip_layer(&star_path); | |
| ctx.set_paint(REBECCA_PURPLE); | |
| ctx.fill_path(&triangle_path); | |
| ctx.pop_layer(); | |
| } | |
| #[vello_test] | |
| fn clip_rectangle_with_star_nonzero(ctx: &mut impl Renderer) { | |
| let rect = Rect::new(0.0, 0.0, 100.0, 100.0); | |
| // Create a self-intersecting star shape that will show the difference between fill rules | |
| let star_path = crossed_line_star(); | |
| // Set the fill rule to NonZero before applying the clip | |
| ctx.set_fill_rule(Fill::NonZero); | |
| // Apply the star as a clip | |
| ctx.push_clip_layer(&star_path); | |
| // Draw a rectangle that should be clipped by the star | |
| // The NonZero fill rule will treat self-intersecting regions as filled | |
| ctx.set_paint(REBECCA_PURPLE); | |
| ctx.fill_rect(&rect); | |
| ctx.pop_layer(); | |
| } | |
| #[vello_test] | |
| fn clip_rectangle_with_star_evenodd(ctx: &mut impl Renderer) { | |
| let rect = Rect::new(0.0, 0.0, 100.0, 100.0); | |
| // Create a self-intersecting star shape that will show the difference between fill rules | |
| let star_path = crossed_line_star(); | |
| // Set the fill rule to EvenOdd before applying the clip | |
| ctx.set_fill_rule(Fill::EvenOdd); | |
| // Apply the star as a clip | |
| ctx.push_clip_layer(&star_path); | |
| // Draw a rectangle that should be clipped by the star | |
| // The EvenOdd rule should create a "hole" in the middle where the paths overlap | |
| ctx.set_paint(REBECCA_PURPLE); | |
| ctx.fill_rect(&rect); | |
| ctx.pop_layer(); | |
| } | |
| #[vello_test(cpu_u8_tolerance = 1)] | |
| fn clip_deeply_nested_circles(ctx: &mut impl Renderer) { | |
| const INITIAL_RADIUS: f64 = 48.0; | |
| const RADIUS_DECREMENT: f64 = 2.5; | |
| const INNER_COUNT: usize = 10; | |
| // `.ceil()` is not constant-evaluatable, so we have to do this at runtime. | |
| let outer_count: usize = | |
| (INITIAL_RADIUS / RADIUS_DECREMENT / INNER_COUNT as f64).ceil() as usize; | |
| const COLORS: [Color; INNER_COUNT] = [ | |
| RED, | |
| DARK_BLUE, | |
| DARK_GREEN, | |
| REBECCA_PURPLE, | |
| BLACK, | |
| BLUE, | |
| GREEN, | |
| RED, | |
| DARK_BLUE, | |
| DARK_GREEN, | |
| ]; | |
| const COVER_RECT: Rect = Rect::new(0.0, 0.0, 100.0, 100.0); | |
| const CENTER: Point = Point::new(50.0, 50.0); | |
| let mut radius = INITIAL_RADIUS; | |
| for _ in 0..outer_count { | |
| for color in COLORS.iter() { | |
| let clip_circle = Circle::new(CENTER, radius).to_path(0.1); | |
| draw_clipping_outline(ctx, &clip_circle); | |
| ctx.push_clip_layer(&clip_circle); | |
| ctx.set_paint(*color); | |
| ctx.fill_rect(&COVER_RECT); | |
| radius -= RADIUS_DECREMENT; | |
| } | |
| } | |
| for _ in 0..outer_count { | |
| for _ in COLORS.iter() { | |
| ctx.pop_layer(); | |
| } | |
| } | |
| } | |
| #[vello_test(cpu_u8_tolerance = 1)] | |
| fn clip_rectangle_and_circle(ctx: &mut impl Renderer) { | |
| // Create first clipping region - a rectangle on the left side | |
| let clip_rect = Rect::new(10.0, 30.0, 50.0, 70.0); | |
| // Create second clipping region - a circle on the right side | |
| let circle_center = Point::new(65.0, 50.0); | |
| let circle_radius = 30.0; | |
| let clip_circle = Circle::new(circle_center, circle_radius).to_path(0.1); | |
| // Draw outlines of our clipping regions to visualize them | |
| let stroke = Stroke::new(1.0); | |
| ctx.set_paint(DARK_BLUE); | |
| ctx.set_stroke(stroke); | |
| ctx.stroke_rect(&clip_rect); | |
| ctx.stroke_path(&clip_circle); | |
| // Apply both clips | |
| ctx.push_clip_layer(&clip_rect.to_path(0.1)); | |
| ctx.push_clip_layer(&clip_circle); | |
| // Then a filled rectangle that covers most of the canvas | |
| let large_rect = Rect::new(0.0, 0.0, 100.0, 100.0); | |
| ctx.set_paint(REBECCA_PURPLE); | |
| ctx.fill_rect(&large_rect); | |
| ctx.pop_layer(); | |
| ctx.pop_layer(); | |
| } | |
| #[vello_test(width = 100, height = 60)] | |
| fn clip_rect_cull_alignment(ctx: &mut impl Renderer) { | |
| const { | |
| assert!( | |
| Tile::HEIGHT >= 4 && Tile::HEIGHT <= 16, | |
| "This test only shows regressions if the tile height remains between 4 and 16 inclusive" | |
| ); | |
| } | |
| // A strip-aligned Y value. | |
| const TOP_Y: f64 = Tile::HEIGHT as f64; | |
| // Another strip-aligned Y value lower down. | |
| // FIXME: make const once MSRV >= 1.90 | |
| let bot_y: f64 = (46. / Tile::HEIGHT as f64).ceil() * Tile::HEIGHT as f64; | |
| let clip_rect = Rect::new(18.0, TOP_Y + 2., 78.0, bot_y - 2.); | |
| ctx.set_paint(DARK_GREEN.with_alpha(0.1)); | |
| ctx.fill_rect(&clip_rect); | |
| // Draw a near-rectangle with slightly curved top and bottom edges to hit interesting | |
| // flattening paths. | |
| // | |
| // The shape's top segment is outside and above the clip path, so it may end up being culled. | |
| // However, the shape does not extend above the strip *row*, so the vertical segments of the | |
| // shape that connect to the horizontal segment do not contribute coarse winding. Therefore, | |
| // the horizontal segment cannot be ignored, as winding between the two vertical segments in | |
| // its strip row needs to be correctly accounted. | |
| // | |
| // The same holds for the bottom segment, except that that case is simpler in our pipeline: | |
| // coarse winding is accounted when geometry of the path passes the strip's top edge, which | |
| // means this strip row will either get a sparse fill, or will get dense tiles due to other | |
| // unculled geometry existing. | |
| let mut path = BezPath::new(); | |
| let top_y = TOP_Y + 1.; | |
| let bot_y = bot_y - 1.; | |
| path.move_to((30.0, bot_y)); | |
| path.line_to((30.0, top_y)); | |
| path.curve_to((45.0, top_y - 0.5), (55.0, top_y - 0.5), (70.0, top_y)); | |
| path.line_to((70.0, bot_y)); | |
| path.curve_to((55.0, bot_y + 0.5), (45.0, bot_y + 0.5), (30.0, bot_y)); | |
| ctx.push_clip_rect(&clip_rect); | |
| ctx.set_paint(REBECCA_PURPLE); | |
| ctx.fill_path(&path); | |
| ctx.pop_clip(); | |
| } | |
| #[vello_test] | |
| fn clip_with_translation(ctx: &mut impl Renderer) { | |
| // Apply a translation transform | |
| ctx.set_transform(Affine::translate((30.0, 30.0))); | |
| // Create and apply a clipping rectangle | |
| let clip_rect = Rect::new(0.0, 0.0, 40.0, 40.0); | |
| draw_clipping_outline(ctx, &clip_rect.to_path(0.1)); | |
| ctx.push_clip_layer(&clip_rect.to_path(0.1)); | |
| // Draw a rectangle that should be clipped | |
| let rect = Rect::new(0.0, 0.0, 100.0, 100.0); | |
| ctx.set_paint(REBECCA_PURPLE); | |
| ctx.fill_rect(&rect); | |
| ctx.pop_layer(); | |
| } | |
| #[vello_test] | |
| fn clip_with_scale(ctx: &mut impl Renderer) { | |
| ctx.set_transform(Affine::scale(2.0)); | |
| // Create and apply a clipping rectangle | |
| let clip_rect = Rect::new(10.0, 10.0, 40.0, 40.0); | |
| draw_clipping_outline(ctx, &clip_rect.to_path(0.1)); | |
| ctx.push_clip_layer(&clip_rect.to_path(0.1)); | |
| // Draw a rectangle that should be clipped | |
| let rect = Rect::new(0.0, 0.0, 100.0, 100.0); | |
| ctx.set_paint(REBECCA_PURPLE); | |
| ctx.fill_rect(&rect); | |
| ctx.pop_layer(); | |
| } | |
| #[vello_test] | |
| fn clip_with_rotate(ctx: &mut impl Renderer) { | |
| ctx.set_transform(Affine::rotate_about( | |
| 45.0 * PI / 180.0, | |
| Point::new(50.0, 50.0), | |
| )); | |
| // Create and apply a clipping rectangle | |
| let clip_rect = Rect::new(20.0, 20.0, 80.0, 80.0); | |
| draw_clipping_outline(ctx, &clip_rect.to_path(0.1)); | |
| ctx.push_clip_layer(&clip_rect.to_path(0.1)); | |
| // Draw a rectangle that should be clipped | |
| let rect = Rect::new(20.0, 20.0, 80.0, 80.0); | |
| ctx.set_paint(REBECCA_PURPLE); | |
| ctx.fill_rect(&rect); | |
| ctx.pop_layer(); | |
| } | |
| #[vello_test] | |
| fn clip_transformed_rect(ctx: &mut impl Renderer) { | |
| let clip_rect = Rect::new(20.0, 20.0, 80.0, 80.0); | |
| draw_clipping_outline(ctx, &clip_rect.to_path(0.1)); | |
| ctx.push_clip_layer(&clip_rect.to_path(0.1)); | |
| ctx.set_transform(Affine::rotate_about( | |
| 45.0 * PI / 180.0, | |
| Point::new(50.0, 50.0), | |
| )); | |
| // Draw a smaller rectangle that should be clipped | |
| let rect = Rect::new(20.0, 20.0, 80.0, 80.0); | |
| ctx.set_paint(REBECCA_PURPLE); | |
| ctx.fill_rect(&rect); | |
| ctx.pop_layer(); | |
| } | |
| #[vello_test] | |
| fn clip_with_multiple_transforms(ctx: &mut impl Renderer) { | |
| // Apply initial transform | |
| ctx.set_transform(Affine::rotate_about( | |
| 45.0 * PI / 180.0, | |
| Point::new(50.0, 50.0), | |
| )); | |
| // Create and apply first clip | |
| let clip_rect1 = Rect::new(20.0, 20.0, 80.0, 80.0); | |
| draw_clipping_outline(ctx, &clip_rect1.to_path(0.1)); | |
| ctx.push_clip_layer(&clip_rect1.to_path(0.1)); | |
| // Apply another transform | |
| ctx.set_transform(Affine::scale(1.5)); | |
| // Create and apply second clip | |
| let clip_rect2 = Rect::new(30.0, 30.0, 70.0, 70.0); | |
| draw_clipping_outline(ctx, &clip_rect2.to_path(0.1)); | |
| ctx.push_clip_layer(&clip_rect2.to_path(0.1)); | |
| // Draw a rectangle that should be clipped by both regions | |
| let rect = Rect::new(0.0, 0.0, 100.0, 100.0); | |
| ctx.set_paint(REBECCA_PURPLE); | |
| ctx.fill_rect(&rect); | |
| ctx.pop_layer(); | |
| ctx.pop_layer(); | |
| } | |
| #[vello_test(gpu_tolerance = 1)] | |
| fn clip_with_save_restore(ctx: &mut impl Renderer) { | |
| // Create first clipping region - a rectangle on the left side | |
| let clip_rect1 = Rect::new(10.0, 30.0, 50.0, 70.0); | |
| draw_clipping_outline(ctx, &clip_rect1.to_path(0.1)); | |
| ctx.push_clip_layer(&clip_rect1.to_path(0.1)); | |
| // Add second clipping region - a circle on the right side | |
| let circle_center = Point::new(65.0, 50.0); | |
| let circle_radius = 30.0; | |
| let clip_circle = Circle::new(circle_center, circle_radius).to_path(0.1); | |
| draw_clipping_outline(ctx, &clip_circle); | |
| ctx.push_clip_layer(&clip_circle); | |
| // Draw a rectangle that should be clipped by both regions | |
| let rect = Rect::new(0.0, 0.0, 100.0, 100.0); | |
| ctx.set_paint(REBECCA_PURPLE); | |
| ctx.fill_rect(&rect); | |
| // Restore to state before second clip | |
| ctx.pop_layer(); | |
| // Draw another rectangle that should only be clipped by the first region | |
| let rect2 = Rect::new(0.0, 0.0, 100.0, 100.0); | |
| ctx.set_paint(DARK_GREEN.with_alpha(0.5)); | |
| ctx.fill_rect(&rect2); | |
| ctx.pop_layer(); | |
| } | |
| #[vello_test] | |
| fn clip_with_opacity(ctx: &mut impl Renderer) { | |
| // Main body of the shape should be RGB 128, 128, 128. Anti-aliased part should be | |
| // 191, 191, 191. | |
| let clip_rect = Rect::new(10.5, 10.5, 89.5, 89.5); | |
| ctx.push_clip_layer(&clip_rect.to_path(0.1)); | |
| ctx.set_paint(BLACK.with_alpha(0.5)); | |
| ctx.fill_rect(&Rect::new(0.0, 0.0, 100.0, 100.0)); | |
| ctx.pop_layer(); | |
| } | |
| fn draw_clipping_outline(ctx: &mut impl Renderer, path: &BezPath) { | |
| let stroke = Stroke::new(1.0); | |
| ctx.set_paint(DARK_BLUE); | |
| ctx.set_stroke(stroke); | |
| ctx.stroke_path(path); | |
| } | |
| // See <https://github.com/linebender/vello/issues/917> | |
| #[vello_test(no_ref)] | |
| fn clip_exceeding_viewport(ctx: &mut impl Renderer) { | |
| ctx.push_clip_layer(&Rect::new(0.0, 0.0, 500.0, 10.0).to_path(0.1)); | |
| ctx.fill_rect(&Rect::new(0.0, 0.0, 100.0, 100.0)); | |
| ctx.pop_layer(); | |
| } | |
| #[vello_test(width = 16, height = 16)] | |
| fn clip_non_isolated_outside_canvas(ctx: &mut impl Renderer) { | |
| // Should be completely clipped. | |
| let clip_rect = Rect::new(0.0, 0.0, 16.0, 16.0); | |
| ctx.push_clip_rect(&clip_rect); | |
| let rect = Rect::new(16.0, -16.0, 32.0, 0.0); | |
| ctx.set_paint(REBECCA_PURPLE); | |
| ctx.fill_rect(&rect); | |
| ctx.pop_clip(); | |
| } | |
| #[vello_test] | |
| fn clip_non_isolated_with_rect(ctx: &mut impl Renderer) { | |
| let clip_rect = Rect::new(10.0, 10.0, 90.0, 90.0); | |
| ctx.push_clip_rect(&clip_rect); | |
| ctx.set_paint(BLUE); | |
| ctx.fill_rect(&Rect::new(0.0, 0.0, 100.0, 100.0)); | |
| ctx.pop_clip(); | |
| } | |
| #[vello_test] | |
| fn clip_non_isolated_with_rotated_rect(ctx: &mut impl Renderer) { | |
| let clip_rect = Rect::new(10.0, 10.0, 90.0, 90.0); | |
| let center = Point::new(50.0, 50.0); | |
| ctx.set_transform(Affine::rotate_about(PI / 2.0, center)); | |
| ctx.push_clip_rect(&clip_rect); | |
| ctx.set_transform(Affine::rotate_about(25.0 * PI / 180.0, center)); | |
| ctx.set_paint(BLUE); | |
| ctx.fill_rect(&Rect::new(0.0, 0.0, 100.0, 100.0)); | |
| ctx.pop_clip(); | |
| } | |
| #[vello_test] | |
| fn clip_non_isolated_with_scaled_rect(ctx: &mut impl Renderer) { | |
| ctx.set_transform( | |
| Affine::translate((50.0, 50.0)) * Affine::scale(4.0) * Affine::translate((-50.0, -50.0)), | |
| ); | |
| let clip_rect = Rect::new(40.0, 40.0, 60.0, 60.0); | |
| ctx.push_clip_rect(&clip_rect); | |
| ctx.set_paint(BLUE); | |
| ctx.fill_rect(&Rect::new(40.0, 40.0, 60.0, 60.0)); | |
| ctx.pop_clip(); | |
| } | |
| #[vello_test] | |
| fn clip_non_isolated_with_aa_with_rect(ctx: &mut impl Renderer) { | |
| let clip_rect = Rect::new(10.5, 10.5, 89.5, 89.5); | |
| ctx.push_clip_rect(&clip_rect); | |
| ctx.set_paint(BLUE); | |
| ctx.fill_rect(&Rect::new(0.0, 0.0, 100.0, 100.0)); | |
| ctx.pop_clip(); | |
| } | |
| #[vello_test] | |
| fn clip_non_isolated_with_aa_with_rect_aa(ctx: &mut impl Renderer) { | |
| let rect = Rect::new(10.5, 10.5, 89.5, 89.5); | |
| ctx.push_clip_rect(&rect); | |
| ctx.set_paint(BLUE); | |
| ctx.fill_rect(&rect); | |
| ctx.pop_clip(); | |
| } | |
| #[vello_test] | |
| fn clip_non_isolated_with_nested_rects_and_pop(ctx: &mut impl Renderer) { | |
| let outer_clip = Rect::new(10.5, 20.25, 89.5, 79.75); | |
| let inner_clip = Rect::new(20.25, 10.5, 79.75, 89.5); | |
| ctx.push_clip_rect(&outer_clip); | |
| ctx.push_clip_rect(&inner_clip); | |
| ctx.set_paint(BLUE); | |
| ctx.fill_rect(&Rect::new(0.0, 0.0, 100.0, 100.0)); | |
| ctx.pop_clip(); | |
| ctx.set_paint(RED); | |
| ctx.fill_rect(&Rect::new(0.0, 35.0, 18.0, 65.0)); | |
| ctx.fill_rect(&Rect::new(82.0, 35.0, 100.0, 65.0)); | |
| ctx.pop_clip(); | |
| } | |
| #[vello_test] | |
| fn clip_non_isolated_mixed_clip_stack_restores_rect(ctx: &mut impl Renderer) { | |
| let clip_rect = Rect::new(15.5, 15.5, 84.5, 84.5); | |
| let clip_circle = Circle::new((50.0, 50.0), 38.0).to_path(0.1); | |
| ctx.push_clip_rect(&clip_rect); | |
| ctx.push_clip_path(&clip_circle); | |
| ctx.set_paint(BLUE); | |
| ctx.fill_rect(&Rect::new(0.0, 0.0, 100.0, 100.0)); | |
| ctx.pop_clip(); | |
| ctx.set_paint(RED); | |
| ctx.fill_rect(&Rect::new(10.0, 10.0, 23.0, 23.0)); | |
| ctx.fill_rect(&Rect::new(77.0, 10.0, 90.0, 23.0)); | |
| ctx.fill_rect(&Rect::new(10.0, 77.0, 23.0, 90.0)); | |
| ctx.fill_rect(&Rect::new(77.0, 77.0, 90.0, 90.0)); | |
| ctx.pop_clip(); | |
| } | |
| #[vello_test] | |
| fn clip_non_isolated_transformed_clip_with_identity_rect(ctx: &mut impl Renderer) { | |
| let clip_rect = Rect::new(10.0, 20.25, 50.0, 79.75); | |
| ctx.set_transform(Affine::translate((5.0, 0.0)) * Affine::scale_non_uniform(1.5, 1.0)); | |
| ctx.push_clip_rect(&clip_rect); | |
| ctx.set_transform(Affine::IDENTITY); | |
| ctx.set_paint(BLUE); | |
| ctx.fill_rect(&Rect::new(30.25, 10.0, 69.75, 90.0)); | |
| ctx.pop_clip(); | |
| } | |
| #[vello_test] | |
| fn clip_non_isolated_rectangle_with_star_evenodd(ctx: &mut impl Renderer) { | |
| let rect = Rect::new(0.0, 0.0, 100.0, 100.0); | |
| let star_path = crossed_line_star(); | |
| ctx.set_fill_rule(Fill::EvenOdd); | |
| ctx.push_clip_path(&star_path); | |
| ctx.set_paint(REBECCA_PURPLE); | |
| ctx.fill_rect(&rect); | |
| ctx.pop_clip(); | |
| } | |
| #[vello_test(cpu_u8_tolerance = 1)] | |
| fn clip_non_isolated_deeply_nested_circles(ctx: &mut impl Renderer) { | |
| const INITIAL_RADIUS: f64 = 48.0; | |
| const RADIUS_DECREMENT: f64 = 2.5; | |
| const INNER_COUNT: usize = 10; | |
| // `.ceil()` is not constant-evaluatable, so we have to do this at runtime. | |
| let outer_count: usize = | |
| (INITIAL_RADIUS / RADIUS_DECREMENT / INNER_COUNT as f64).ceil() as usize; | |
| const COLORS: [Color; INNER_COUNT] = [ | |
| RED, | |
| DARK_BLUE, | |
| DARK_GREEN, | |
| REBECCA_PURPLE, | |
| BLACK, | |
| BLUE, | |
| GREEN, | |
| RED, | |
| DARK_BLUE, | |
| DARK_GREEN, | |
| ]; | |
| const COVER_RECT: Rect = Rect::new(0.0, 0.0, 100.0, 100.0); | |
| const CENTER: Point = Point::new(50.0, 50.0); | |
| let mut radius = INITIAL_RADIUS; | |
| for _ in 0..outer_count { | |
| for color in COLORS.iter() { | |
| let clip_circle = Circle::new(CENTER, radius).to_path(0.1); | |
| draw_clipping_outline(ctx, &clip_circle); | |
| ctx.push_clip_path(&clip_circle); | |
| ctx.set_paint(*color); | |
| ctx.fill_rect(&COVER_RECT); | |
| radius -= RADIUS_DECREMENT; | |
| } | |
| } | |
| for _ in 0..outer_count { | |
| for _ in COLORS.iter() { | |
| ctx.pop_clip(); | |
| } | |
| } | |
| } | |
| #[vello_test(width = 600, height = 200)] | |
| fn clip_with_linear_gradient_fill(ctx: &mut impl Renderer) { | |
| // Border to see crop. | |
| ctx.fill_rect(&Rect::new(0., 0., 600., 200.)); | |
| // Crop 10px border around image. | |
| let clip = Rect::new(10., 10., 590., 190.0); | |
| ctx.push_clip_layer(&clip.to_path(0.)); | |
| // Fill a full screen rect with a gradient. | |
| let box_rect = Rect::new(0., 0., 600.0, 200.0); | |
| let gradient = Gradient { | |
| kind: LinearGradientPosition { | |
| start: Point::new(100.0, 100.0), | |
| end: Point::new(300.0, 100.0), | |
| } | |
| .into(), | |
| stops: stops_green_blue(), | |
| ..Default::default() | |
| }; | |
| ctx.set_paint(gradient); | |
| ctx.fill_rect(&box_rect); | |
| ctx.pop_layer(); | |
| } | |
| #[vello_test(width = 600, height = 200)] | |
| fn clip_with_radial_gradient_fill(ctx: &mut impl Renderer) { | |
| // Border to see crop. | |
| ctx.fill_rect(&Rect::new(0., 0., 600., 200.)); | |
| // Crop 10px border around image. | |
| let clip = Rect::new(10., 10., 590., 190.0); | |
| ctx.push_clip_layer(&clip.to_path(0.)); | |
| // Fill a full screen rect with a gradient. | |
| let box_rect = Rect::new(0., 0., 600.0, 200.0); | |
| let gradient = Gradient { | |
| kind: RadialGradientPosition { | |
| start_center: Point::new(300.0, 100.0), | |
| start_radius: 0.0, | |
| end_center: Point::new(300.0, 100.0), | |
| end_radius: 150.0, | |
| } | |
| .into(), | |
| stops: stops_green_blue(), | |
| ..Default::default() | |
| }; | |
| ctx.set_paint(gradient); | |
| ctx.fill_rect(&box_rect); | |
| ctx.pop_layer(); | |
| } | |
| #[vello_test(width = 600, height = 200)] | |
| fn clip_with_sweep_gradient_fill(ctx: &mut impl Renderer) { | |
| // Border to see crop. | |
| ctx.fill_rect(&Rect::new(0., 0., 600., 200.)); | |
| // Crop 10px border around image. | |
| let clip = Rect::new(10., 10., 590., 190.0); | |
| ctx.push_clip_layer(&clip.to_path(0.)); | |
| // Fill a full screen rect with a gradient. | |
| let box_rect = Rect::new(0., 0., 600.0, 200.0); | |
| let gradient = Gradient { | |
| kind: SweepGradientPosition { | |
| center: Point::new(300.0, 100.0), | |
| start_angle: 0.0, | |
| end_angle: std::f32::consts::TAU, | |
| } | |
| .into(), | |
| stops: stops_green_blue(), | |
| ..Default::default() | |
| }; | |
| ctx.set_paint(gradient); | |
| ctx.fill_rect(&box_rect); | |
| ctx.pop_layer(); | |
| } | |
| #[vello_test(width = 300, height = 30)] | |
| fn clip_layer_encloses_viewport_via_left_cull(ctx: &mut impl Renderer) { | |
| let clip = Rect::new(-100.0, -100.0, 400.0, 130.0).to_path(0.1); | |
| ctx.push_clip_layer(&clip); | |
| ctx.set_paint(REBECCA_PURPLE); | |
| ctx.fill_rect(&Rect::new(0.0, 0.0, 300.0, 30.0)); | |
| ctx.pop_layer(); | |
| } |