79 lines
3.3 KiB
Rust
79 lines
3.3 KiB
Rust
//! Pixel tests for dashes fitted to corners and for width profiles with dashes and joins, drawn
|
|
//! on the canvas from the shared stroke geometry (`vectorcraft_effects::stroke`).
|
|
|
|
use vectorcraft_color::{Color, Paint};
|
|
use vectorcraft_doc::{Appearance, AppearanceItem, Dash, Document, LineJoin, StrokeLayer, WidthProfile};
|
|
use vectorcraft_geom::{PathData, Point, Rect, SubPath, shapes};
|
|
use vectorcraft_testkit::fixtures::DocBuilder;
|
|
use vectorcraft_testkit::raster::{Image, render_region};
|
|
|
|
/// Pixels per point in these tests.
|
|
const S: f64 = 4.0;
|
|
|
|
fn doc(path: PathData, width: f64, f: impl FnOnce(&mut StrokeLayer)) -> Document {
|
|
let mut st = StrokeLayer::new(Paint::solid(Color::BLACK), width);
|
|
f(&mut st);
|
|
let mut b = DocBuilder::new(160.0, 120.0);
|
|
b.path(path, Appearance { items: vec![AppearanceItem::Stroke(st)], ..Default::default() }, |_| {});
|
|
b.build()
|
|
}
|
|
|
|
fn render(d: &Document) -> Image {
|
|
render_region(d, Rect::new(0.0, 0.0, 160.0, 120.0), S)
|
|
}
|
|
|
|
/// Is the pixel at document point (x, y) dark?
|
|
fn dark(img: &Image, x: f64, y: f64) -> bool {
|
|
img.over_white((x * S) as u32, (y * S) as u32)[0] < 128
|
|
}
|
|
|
|
fn rect_doc(align_corners: bool) -> Document {
|
|
doc(shapes::rectangle(Rect::new(20.0, 20.0, 120.0, 70.0)), 2.0, |s| s.dash = Some(Dash { pattern: vec![12.0, 6.0], offset: 0.0, align_corners }))
|
|
}
|
|
|
|
#[test]
|
|
fn fitted_dashes_wrap_every_corner_of_a_rectangle() {
|
|
let img = render(&rect_doc(true));
|
|
// Half a dash (6 · 100/108) runs each way from every corner, then a gap of the same length.
|
|
let half = 6.0 * 100.0 / 108.0;
|
|
for (c, dirs) in [
|
|
((20.0, 20.0), [(1.0, 0.0), (0.0, 1.0)]),
|
|
((120.0, 20.0), [(-1.0, 0.0), (0.0, 1.0)]),
|
|
((120.0, 70.0), [(-1.0, 0.0), (0.0, -1.0)]),
|
|
((20.0, 70.0), [(1.0, 0.0), (0.0, -1.0)]),
|
|
] {
|
|
for (dx, dy) in dirs {
|
|
let at = |d: f64| (c.0 + dx * d, c.1 + dy * d);
|
|
let (x, y) = at(half / 2.0);
|
|
assert!(dark(&img, x, y), "dash beside corner {c:?}");
|
|
let (x, y) = at(half * 1.5);
|
|
assert!(!dark(&img, x, y), "gap after corner {c:?} along ({dx}, {dy})");
|
|
}
|
|
}
|
|
// Exact dashes keep their lengths instead (the exact golden covers how they look).
|
|
assert_ne!(render(&rect_doc(false)).rgba, img.rgba);
|
|
}
|
|
|
|
#[test]
|
|
fn dashes_of_a_lens_stroke_widen_towards_the_middle() {
|
|
let img = render(&doc(shapes::line(Point::new(30.0, 60.0), Point::new(130.0, 60.0)), 10.0, |s| {
|
|
s.profile = Some(WidthProfile::lens());
|
|
s.dash = Some(Dash { pattern: vec![10.0, 10.0], offset: 0.0, align_corners: false });
|
|
}));
|
|
// The dash over 40..50 of the 100 pt line is nearly full width, the one over 0..10 thin.
|
|
assert!(dark(&img, 75.0, 60.0 - 3.8) && dark(&img, 75.0, 60.0 + 3.8));
|
|
assert!(dark(&img, 35.0, 60.0) && !dark(&img, 35.0, 60.0 - 1.5));
|
|
assert!(!dark(&img, 45.0, 60.0), "gap");
|
|
}
|
|
|
|
#[test]
|
|
fn a_round_join_on_a_profile_stroke_has_no_spike() {
|
|
let l = SubPath::polyline(&[Point::new(20.0, 40.0), Point::new(100.0, 40.0), Point::new(100.0, 110.0)], false);
|
|
let img = render(&doc(PathData::single(l), 20.0, |s| {
|
|
s.profile = Some(WidthProfile { points: vec![(0.0, 1.0, 1.0), (1.0, 1.0, 1.0)] });
|
|
s.join = LineJoin::Round;
|
|
}));
|
|
assert!(dark(&img, 106.0, 34.0), "the round join covers the corner");
|
|
assert!(!dark(&img, 109.0, 31.0), "but not the miter's point");
|
|
}
|