Skip to content

Commit

Permalink
Introduce test scenes that demonstrates conflation artifacts
Browse files Browse the repository at this point in the history
Added two scenes that demonstrate conflation artifacts as described in
linebender#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).
  • Loading branch information
armansito committed Feb 3, 2023
1 parent 020a7f5 commit d2f1050
Showing 1 changed file with 164 additions and 0 deletions.
164 changes: 164 additions & 0 deletions examples/scenes/src/test_scenes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -403,6 +405,168 @@ 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 mut y = N;

// Two adjacent triangles touching at diagonal edge with opposing winding numbers
sb.fill(
Fill::NonZero,
Affine::translate((N, 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, non-axis-aligned seam
y += S * N + 10.0;
sb.fill(
Fill::EvenOdd,
Affine::translate((N, y)) * scale,
Color::RED,
None,
&Rect::new(0.0, 0.0, N, N),
);
sb.fill(
Fill::EvenOdd,
Affine::translate((N, y)) * scale,
Color::GREEN,
None,
&[
// left rect
MoveTo((0.0, 0.0).into()),
LineTo((0.0, N).into()),
LineTo((N * 0.5 - 0.1, 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 - 0.1, N).into()),
],
);

// Adjacent rects, same winding, non-axis-aligned seam
y += S * N + 10.0;
sb.fill(
Fill::EvenOdd,
Affine::translate((N, y)) * scale,
Color::RED,
None,
&Rect::new(0.0, 0.0, N, N),
);
sb.fill(
Fill::EvenOdd,
Affine::translate((N, y)) * scale,
Color::GREEN,
None,
&[
// left rect
MoveTo((0.0, 0.0).into()),
LineTo((0.0, N).into()),
LineTo((N * 0.5 - 0.1, N).into()),
LineTo((N * 0.5, 0.0).into()),
// right rect
MoveTo((N * 0.5, 0.0).into()),
LineTo((N * 0.5 - 0.1, 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())
}
Expand Down

0 comments on commit d2f1050

Please sign in to comment.