Skip to content

Commit

Permalink
Merge pull request #388 from armansito/pr-stroke-tests
Browse files Browse the repository at this point in the history
[test_scenes] Tests scenes for strokes
  • Loading branch information
armansito authored Oct 19, 2023
2 parents 84fe030 + d538f65 commit a295371
Showing 1 changed file with 197 additions and 1 deletion.
198 changes: 197 additions & 1 deletion examples/scenes/src/test_scenes.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
// Copyright 2022 The Vello authors
// SPDX-License-Identifier: Apache-2.0 OR MIT

use crate::{ExampleScene, SceneConfig, SceneParams, SceneSet};
use vello::kurbo::{Affine, BezPath, Cap, Ellipse, PathEl, Point, Rect, Stroke};
use vello::kurbo::{Affine, BezPath, Cap, Ellipse, Join, PathEl, Point, Rect, Stroke};
use vello::peniko::*;
use vello::*;

Expand Down Expand Up @@ -31,6 +34,8 @@ pub fn test_scenes() -> SceneSet {
scene!(splash_with_tiger(), "splash_with_tiger", false),
scene!(crate::mmark::MMark::new(80_000), "mmark", false),
scene!(funky_paths),
scene!(stroke_styles),
scene!(tricky_strokes),
scene!(cardioid_and_friends),
scene!(animated_text: animated),
scene!(gradient_extend),
Expand Down Expand Up @@ -91,6 +96,197 @@ fn funky_paths(sb: &mut SceneBuilder, _: &mut SceneParams) {
);
}

fn stroke_styles(sb: &mut SceneBuilder, params: &mut SceneParams) {
use PathEl::*;
let colors = [
Color::rgb8(140, 181, 236),
Color::rgb8(246, 236, 202),
Color::rgb8(201, 147, 206),
Color::rgb8(150, 195, 160),
];
let simple_stroke = [LineTo((100., 0.).into())];
let join_stroke = [
CurveTo((20., 0.).into(), (42.5, 5.).into(), (50., 25.).into()),
CurveTo((57.5, 5.).into(), (80., 0.).into(), (100., 0.).into()),
];
let miter_stroke = [LineTo((90., 21.).into()), LineTo((0., 42.).into())];
let cap_styles = [Cap::Butt, Cap::Square, Cap::Round];
let join_styles = [Join::Bevel, Join::Miter, Join::Round];
let miter_limits = [4., 5., 0.1, 10.];

// Simple strokes with cap combinations
let t = Affine::translate((60., 40.)) * Affine::scale(2.);
let mut y = 0.;
let mut color_idx = 0;
for start in cap_styles {
for end in cap_styles {
params.text.add(
sb,
None,
12.,
None,
Affine::translate((0., y)) * t,
&format!("Start cap: {:?}, End cap: {:?}", start, end),
);
sb.stroke(
&Stroke::new(20.).with_start_cap(start).with_end_cap(end),
Affine::translate((0., y + 30.)) * t,
colors[color_idx],
None,
&simple_stroke,
);
y += 180.;
color_idx = (color_idx + 1) % colors.len();
}
}

// Cap and join combinations
let t = Affine::translate((500., 0.)) * t;
y = 0.;
for cap in cap_styles {
for join in join_styles {
params.text.add(
sb,
None,
12.,
None,
Affine::translate((0., y)) * t,
&format!("Caps: {:?}, Joins: {:?}", cap, join),
);
sb.stroke(
&Stroke::new(20.).with_caps(cap).with_join(join),
Affine::translate((0., y + 30.)) * t,
colors[color_idx],
None,
&join_stroke,
);
y += 185.;
color_idx = (color_idx + 1) % colors.len();
}
}

// Miter limit
let t = Affine::translate((500., 0.)) * t;
y = 0.;
for ml in miter_limits {
params.text.add(
sb,
None,
12.,
None,
Affine::translate((0., y)) * t,
&format!("Miter limit: {}", ml),
);
sb.stroke(
&Stroke::new(10.)
.with_caps(Cap::Butt)
.with_join(Join::Miter)
.with_miter_limit(ml),
Affine::translate((0., y + 30.)) * t,
colors[color_idx],
None,
&miter_stroke,
);
y += 180.;
color_idx = (color_idx + 1) % colors.len();
}
}

// This test has been adapted from Skia's "trickycubicstrokes" GM slide which can be found at
// `github.com/google/skia/blob/0d4d11451c4f4e184305cbdbd67f6b3edfa4b0e3/gm/trickycubicstrokes.cpp`
fn tricky_strokes(sb: &mut SceneBuilder, _: &mut SceneParams) {
use PathEl::*;
let colors = [
Color::rgb8(140, 181, 236),
Color::rgb8(246, 236, 202),
Color::rgb8(201, 147, 206),
Color::rgb8(150, 195, 160),
];

const CELL_SIZE: f64 = 200.;
const STROKE_WIDTH: f64 = 30.;
const NUM_COLS: usize = 5;

fn stroke_bounds(pts: &[(f64, f64); 4]) -> Rect {
use kurbo::{CubicBez, Shape};
CubicBez::new(pts[0], pts[1], pts[2], pts[3])
.bounding_box()
.inflate(STROKE_WIDTH, STROKE_WIDTH)
}

fn map_rect_to_rect(src: &Rect, dst: &Rect) -> (Affine, f64) {
let (scale, x_larger) = {
let sx = dst.width() / src.width();
let sy = dst.height() / src.height();
(sx.min(sy), sx > sy)
};
let tx = dst.x0 - src.x0 * scale;
let ty = dst.y0 - src.y0 * scale;
let (tx, ty) = if x_larger {
(tx + 0.5 * (dst.width() - src.width() * scale), ty)
} else {
(tx, ty + 0.5 * (dst.height() - src.height() * scale))
};
(Affine::new([scale, 0.0, 0.0, scale, tx, ty]), scale)
}

let tricky_cubics = [
[(122., 737.), (348., 553.), (403., 761.), (400., 760.)],
[(244., 520.), (244., 518.), (1141., 634.), (394., 688.)],
[(550., 194.), (138., 130.), (1035., 246.), (288., 300.)],
[(226., 733.), (556., 779.), (-43., 471.), (348., 683.)],
[(268., 204.), (492., 304.), (352., 23.), (433., 412.)],
[(172., 480.), (396., 580.), (256., 299.), (338., 677.)],
[(731., 340.), (318., 252.), (1026., -64.), (367., 265.)],
[(475., 708.), (62., 620.), (770., 304.), (220., 659.)],
[(0., 0.), (128., 128.), (128., 0.), (0., 128.)], // Perfect cusp
[(0., 0.01), (128., 127.999), (128., 0.01), (0., 127.99)], // Near-cusp
];

// FIXME: The following curves all cause a crash due to a stack overflow following an
// infinite recursion in `kurbo::fit::fit_to_bezpath_rec` which gets called by
// `SceneBuilder::stroke` below. Disabling these tests until kurbo handles these
// gracefully. Move these into `tricky_cubics` above once they are fixed.
let _broken_cubics = [
[(0., -0.01), (128., 128.001), (128., -0.01), (0., 128.001)], // Near-cusp
[(0., 0.), (0., -10.), (0., -10.), (0., 10.)], // Flat line with 180
[(10., 0.), (0., 0.), (20., 0.), (10., 0.)], // Flat line with 2 180s
[(39., -39.), (40., -40.), (40., -40.), (0., 0.)], // Flat diagonal with 180
[(40., 40.), (0., 0.), (200., 200.), (0., 0.)], // Diag w/ an internal 180
[(0., 0.), (1e-2, 0.), (-1e-2, 0.), (0., 0.)], // Circle
// Flat line with no turns:
[
(400.75, 100.05),
(400.75, 100.05),
(100.05, 300.95),
(100.05, 300.95),
],
[(0.5, 0.), (0., 0.), (20., 0.), (10., 0.)], // Flat line with 2 180s
[(10., 0.), (0., 0.), (10., 0.), (10., 0.)], // Flat line with a 180
];
let mut color_idx = 0;
for (i, cubic) in tricky_cubics.into_iter().enumerate() {
let x = (i % NUM_COLS) as f64 * CELL_SIZE;
let y = (i / NUM_COLS) as f64 * CELL_SIZE;
let cell = Rect::new(x, y, x + CELL_SIZE, y + CELL_SIZE);
let bounds = stroke_bounds(&cubic);
let (t, s) = map_rect_to_rect(&bounds, &cell);
sb.stroke(
&Stroke::new(STROKE_WIDTH / s)
.with_caps(Cap::Butt)
.with_join(Join::Miter),
t,
colors[color_idx],
None,
&[
MoveTo(cubic[0].into()),
CurveTo(cubic[1].into(), cubic[2].into(), cubic[3].into()),
],
);
color_idx = (color_idx + 1) % colors.len();
}
}

fn cardioid_and_friends(sb: &mut SceneBuilder, _: &mut SceneParams) {
render_cardioid(sb);
render_clip_test(sb);
Expand Down

0 comments on commit a295371

Please sign in to comment.