From dc2e6690c48e238c7afdee37cd0102c4d21a5463 Mon Sep 17 00:00:00 2001 From: Arman Uguray Date: Fri, 3 Feb 2023 00:17:25 -0800 Subject: [PATCH 1/3] Introduce test scenes that demonstrates conflation artifacts Added two scenes that demonstrate conflation artifacts as described in https://github.com/linebender/vello/issues/49. The first scene demonstrates adjacent triangles and rects that belong to the same path and use opposite winding. The second scene demonstrates strokes with overlapping square caps (these strokes are currently expressed as rects painted with the NonZero fill rule). --- examples/scenes/src/test_scenes.rs | 165 +++++++++++++++++++++++++++++ 1 file changed, 165 insertions(+) diff --git a/examples/scenes/src/test_scenes.rs b/examples/scenes/src/test_scenes.rs index f86f463..9784429 100644 --- a/examples/scenes/src/test_scenes.rs +++ b/examples/scenes/src/test_scenes.rs @@ -30,6 +30,8 @@ pub fn test_scenes() -> SceneSet { scene!(animated_text: animated), scene!(brush_transform: animated), scene!(blend_grid), + scene!(conflation_artifacts), + scene!(labyrinth), ]; #[cfg(target_arch = "wasm32")] scenes.push(ExampleScene { @@ -403,6 +405,169 @@ fn blend_square(blend: BlendMode) -> SceneFragment { fragment } +fn conflation_artifacts(sb: &mut SceneBuilder, _: &mut SceneParams) { + use PathEl::*; + const N: f64 = 50.0; + const S: f64 = 4.0; + + let scale = Affine::scale(S); + let x = N + 0.5; // Fraction pixel offset reveals the problem on axis-aligned edges. + let mut y = N; + + // Two adjacent triangles touching at diagonal edge with opposing winding numbers + sb.fill( + Fill::NonZero, + Affine::translate((x, y)) * scale, + Color::RED, + None, + &[ + // triangle 1 + MoveTo((0.0, 0.0).into()), + LineTo((N, N).into()), + LineTo((0.0, N).into()), + LineTo((0.0, 0.0).into()), + // triangle 2 + MoveTo((0.0, 0.0).into()), + LineTo((N, N).into()), + LineTo((N, 0.0).into()), + LineTo((0.0, 0.0).into()), + ], + ); + + // Adjacent rects, opposite winding + y += S * N + 10.0; + sb.fill( + Fill::EvenOdd, + Affine::translate((x, y)) * scale, + Color::RED, + None, + &Rect::new(0.0, 0.0, N, N), + ); + sb.fill( + Fill::EvenOdd, + Affine::translate((x, y)) * scale, + Color::GREEN, + None, + &[ + // left rect + MoveTo((0.0, 0.0).into()), + LineTo((0.0, N).into()), + LineTo((N * 0.5, N).into()), + LineTo((N * 0.5, 0.0).into()), + // right rect + MoveTo((N * 0.5, 0.0).into()), + LineTo((N, 0.0).into()), + LineTo((N, N).into()), + LineTo((N * 0.5, N).into()), + ], + ); + + // Adjacent rects, same winding + y += S * N + 10.0; + sb.fill( + Fill::EvenOdd, + Affine::translate((x, y)) * scale, + Color::RED, + None, + &Rect::new(0.0, 0.0, N, N), + ); + sb.fill( + Fill::EvenOdd, + Affine::translate((x, y)) * scale, + Color::GREEN, + None, + &[ + // left rect + MoveTo((0.0, 0.0).into()), + LineTo((0.0, N).into()), + LineTo((N * 0.5, N).into()), + LineTo((N * 0.5, 0.0).into()), + // right rect + MoveTo((N * 0.5, 0.0).into()), + LineTo((N * 0.5, N).into()), + LineTo((N, N).into()), + LineTo((N, 0.0).into()), + ], + ); +} + +fn labyrinth(sb: &mut SceneBuilder, _: &mut SceneParams) { + use PathEl::*; + + let rows: &[[u8; 12]] = &[ + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1], + [0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1], + [1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0], + [0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1], + [1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0], + [0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0], + [1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1], + [0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1], + [0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + ]; + let cols: &[[u8; 10]] = &[ + [1, 1, 1, 1, 0, 1, 1, 1, 1, 1], + [0, 0, 1, 0, 0, 0, 1, 1, 1, 0], + [0, 1, 1, 0, 1, 1, 1, 0, 0, 1], + [1, 1, 0, 0, 0, 0, 1, 0, 1, 0], + [0, 0, 1, 0, 1, 0, 0, 0, 0, 1], + [0, 0, 1, 1, 1, 0, 0, 0, 1, 0], + [0, 1, 0, 1, 1, 1, 0, 0, 0, 0], + [1, 1, 1, 0, 1, 1, 1, 0, 1, 0], + [1, 1, 0, 1, 1, 0, 0, 0, 1, 0], + [0, 0, 1, 0, 0, 0, 0, 0, 0, 1], + [0, 0, 1, 1, 0, 0, 0, 0, 1, 0], + [0, 0, 0, 0, 0, 0, 1, 0, 0, 1], + [1, 1, 1, 1, 1, 1, 0, 1, 1, 1], + ]; + let mut path = BezPath::new(); + for (y, row) in rows.iter().enumerate() { + for (x, flag) in row.iter().enumerate() { + let x = x as f64; + let y = y as f64; + if *flag == 1 { + path.push(MoveTo((x - 0.1, y + 0.1).into())); + path.push(LineTo((x + 1.1, y + 0.1).into())); + path.push(LineTo((x + 1.1, y - 0.1).into())); + path.push(LineTo((x - 0.1, y - 0.1).into())); + + // The above is equivalent to the following stroke with width 0.2 and square + // caps. + //path.push(MoveTo((x, y).into())); + //path.push(LineTo((x + 1.0, y).into())); + } + } + } + for (x, col) in cols.iter().enumerate() { + for (y, flag) in col.iter().enumerate() { + let x = x as f64; + let y = y as f64; + if *flag == 1 { + path.push(MoveTo((x - 0.1, y - 0.1).into())); + path.push(LineTo((x - 0.1, y + 1.1).into())); + path.push(LineTo((x + 0.1, y + 1.1).into())); + path.push(LineTo((x + 0.1, y - 0.1).into())); + // The above is equivalent to the following stroke with width 0.2 and square + // caps. + //path.push(MoveTo((x, y).into())); + //path.push(LineTo((x, y + 1.0).into())); + } + } + } + + // Note the artifacts are clearly visible at a fractional pixel offset/translation. They + // disappear if the translation amount below is a whole number. + sb.fill( + Fill::NonZero, + Affine::translate((20.5, 20.5)) * Affine::scale(80.0), + Color::rgba8(0x70, 0x80, 0x80, 0xff), + None, + &path, + ) +} + fn around_center(xform: Affine, center: Point) -> Affine { Affine::translate(center.to_vec2()) * xform * Affine::translate(-center.to_vec2()) } From fff43bba0d886de3d98aa8c1ba869828d4c064ad Mon Sep 17 00:00:00 2001 From: Arman Uguray Date: Fri, 3 Feb 2023 09:34:12 -0800 Subject: [PATCH 2/3] ran cargo fmt; fixed spelling in comment --- examples/scenes/src/test_scenes.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/scenes/src/test_scenes.rs b/examples/scenes/src/test_scenes.rs index 9784429..c666663 100644 --- a/examples/scenes/src/test_scenes.rs +++ b/examples/scenes/src/test_scenes.rs @@ -411,7 +411,7 @@ fn conflation_artifacts(sb: &mut SceneBuilder, _: &mut SceneParams) { const S: f64 = 4.0; let scale = Affine::scale(S); - let x = N + 0.5; // Fraction pixel offset reveals the problem on axis-aligned edges. + let x = N + 0.5; // Fractional pixel offset reveals the problem on axis-aligned edges. let mut y = N; // Two adjacent triangles touching at diagonal edge with opposing winding numbers From 97e6e12799092f1cc7a9cd0eeb11abe88584fb42 Mon Sep 17 00:00:00 2001 From: Arman Uguray Date: Fri, 3 Feb 2023 12:27:42 -0800 Subject: [PATCH 3/3] Use accessible colors that maintain contrast in the conflation scene --- examples/scenes/src/test_scenes.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/examples/scenes/src/test_scenes.rs b/examples/scenes/src/test_scenes.rs index c666663..47a4ff9 100644 --- a/examples/scenes/src/test_scenes.rs +++ b/examples/scenes/src/test_scenes.rs @@ -414,11 +414,14 @@ fn conflation_artifacts(sb: &mut SceneBuilder, _: &mut SceneParams) { let x = N + 0.5; // Fractional pixel offset reveals the problem on axis-aligned edges. let mut y = N; + let bg_color = Color::rgb8(255, 194, 19); + let fg_color = Color::rgb8(12, 165, 255); + // Two adjacent triangles touching at diagonal edge with opposing winding numbers sb.fill( Fill::NonZero, Affine::translate((x, y)) * scale, - Color::RED, + fg_color, None, &[ // triangle 1 @@ -439,14 +442,14 @@ fn conflation_artifacts(sb: &mut SceneBuilder, _: &mut SceneParams) { sb.fill( Fill::EvenOdd, Affine::translate((x, y)) * scale, - Color::RED, + bg_color, None, &Rect::new(0.0, 0.0, N, N), ); sb.fill( Fill::EvenOdd, Affine::translate((x, y)) * scale, - Color::GREEN, + fg_color, None, &[ // left rect @@ -467,14 +470,14 @@ fn conflation_artifacts(sb: &mut SceneBuilder, _: &mut SceneParams) { sb.fill( Fill::EvenOdd, Affine::translate((x, y)) * scale, - Color::RED, + bg_color, None, &Rect::new(0.0, 0.0, N, N), ); sb.fill( Fill::EvenOdd, Affine::translate((x, y)) * scale, - Color::GREEN, + fg_color, None, &[ // left rect