use super::*;
use agg_rust::basics::PATH_FLAGS_NONE;
fn fill_path_reference(
buf: &mut LcdBuffer,
path: &mut PathStorage,
color: Color,
transform: &TransAffine,
clip: Option<(f64, f64, f64, f64)>,
rule: FillRule,
) {
if buf.width() == 0 || buf.height() == 0 {
return;
}
let mut builder = LcdMaskBuilder::new(buf.width(), buf.height())
.with_clip(clip)
.with_fill_rule(rule);
builder.with_paths(transform, |add| {
add(path);
});
let mask = builder.finalize();
let clip_i = clip.map(rect_to_pixel_clip);
buf.composite_mask(&mask, color, 0, 0, clip_i);
}
#[allow(clippy::too_many_arguments)]
fn assert_fill_equiv<P: FnMut() -> PathStorage>(
w: u32,
h: u32,
prefill: Option<Color>,
mut make_path: P,
color: Color,
transform: &TransAffine,
clip: Option<(f64, f64, f64, f64)>,
rule: FillRule,
label: &str,
) {
let mut a = LcdBuffer::new(w, h);
let mut b = LcdBuffer::new(w, h);
if let Some(c) = prefill {
a.clear(c);
b.clear(c);
}
let mut pa = make_path();
let mut pb = make_path();
a.fill_path(&mut pa, color, transform, clip, rule);
fill_path_reference(&mut b, &mut pb, color, transform, clip, rule);
assert_eq!(
a.color_plane(),
b.color_plane(),
"color plane mismatch: {label}"
);
assert_eq!(
a.alpha_plane(),
b.alpha_plane(),
"alpha plane mismatch: {label}"
);
}
fn rect(x1: f64, y1: f64, x2: f64, y2: f64) -> PathStorage {
let mut p = PathStorage::new();
p.move_to(x1, y1);
p.line_to(x2, y1);
p.line_to(x2, y2);
p.line_to(x1, y2);
p.close_polygon(PATH_FLAGS_NONE);
p
}
#[test]
fn fill_path_bbox_axis_aligned_rect() {
assert_fill_equiv(
120,
60,
Some(Color::white()),
|| rect(12.3, 8.7, 96.4, 30.2),
Color::rgba(0.1, 0.2, 0.3, 1.0),
&TransAffine::new(),
None,
FillRule::NonZero,
"axis-aligned strip rect",
);
}
#[test]
fn fill_path_bbox_rect_with_partial_clip() {
assert_fill_equiv(
120,
60,
Some(Color::white()),
|| rect(12.3, 8.7, 96.4, 30.2),
Color::black(),
&TransAffine::new(),
Some((20.0, 5.0, 40.0, 40.0)),
FillRule::NonZero,
"rect with partial clip",
);
}
#[test]
fn fill_path_bbox_rotated_scaled_triangle() {
let mut t = TransAffine::new_rotation(0.35);
t.scale(1.6, 1.2);
t.translate(55.0, 30.0);
let make = || {
let mut p = PathStorage::new();
p.move_to(-15.0, -10.0);
p.line_to(18.0, -6.0);
p.line_to(2.0, 16.0);
p.close_polygon(PATH_FLAGS_NONE);
p
};
assert_fill_equiv(
130,
70,
Some(Color::white()),
make,
Color::rgba(0.9, 0.1, 0.2, 1.0),
&t,
None,
FillRule::NonZero,
"rotated + scaled triangle",
);
}
#[test]
fn fill_path_bbox_cubic_curve() {
let make = || {
let mut p = PathStorage::new();
p.move_to(15.0, 15.0);
p.curve4(22.0, 48.0, 70.0, 48.0, 78.0, 15.0);
p.line_to(78.0, 15.0);
p.close_polygon(PATH_FLAGS_NONE);
p
};
assert_fill_equiv(
100,
60,
Some(Color::white()),
make,
Color::rgba(0.2, 0.5, 0.1, 1.0),
&TransAffine::new(),
None,
FillRule::NonZero,
"cubic curve control-hull bbox",
);
}
#[test]
fn fill_path_bbox_quadratic_curve() {
let make = || {
let mut p = PathStorage::new();
p.move_to(10.0, 12.0);
p.curve3(40.0, 45.0, 70.0, 12.0);
p.line_to(70.0, 12.0);
p.close_polygon(PATH_FLAGS_NONE);
p
};
assert_fill_equiv(
90,
55,
Some(Color::white()),
make,
Color::black(),
&TransAffine::new(),
None,
FillRule::NonZero,
"quadratic curve control-hull bbox",
);
}
#[test]
fn fill_path_bbox_partially_outside() {
assert_fill_equiv(
60,
40,
Some(Color::white()),
|| rect(-12.5, -6.5, 18.5, 22.5),
Color::black(),
&TransAffine::new(),
None,
FillRule::NonZero,
"rect partially off bottom-left",
);
}
#[test]
fn fill_path_bbox_entirely_outside() {
assert_fill_equiv(
60,
40,
Some(Color::white()),
|| rect(200.0, 200.0, 260.0, 260.0),
Color::black(),
&TransAffine::new(),
None,
FillRule::NonZero,
"rect entirely off top-right",
);
assert_fill_equiv(
60,
40,
Some(Color::white()),
|| rect(-260.0, -260.0, -200.0, -200.0),
Color::black(),
&TransAffine::new(),
None,
FillRule::NonZero,
"rect entirely off bottom-left",
);
}
#[test]
fn fill_path_bbox_flush_against_edges() {
let w = 50u32;
let h = 30u32;
let cases: [(f64, f64, f64, f64, &str); 4] = [
(0.0, 8.0, 10.0, 22.0, "flush left"),
(w as f64 - 10.0, 8.0, w as f64, 22.0, "flush right"),
(10.0, 0.0, 40.0, 8.0, "flush bottom"),
(10.0, h as f64 - 8.0, 40.0, h as f64, "flush top"),
];
for (x1, y1, x2, y2, label) in cases {
assert_fill_equiv(
w,
h,
Some(Color::white()),
|| rect(x1, y1, x2, y2),
Color::black(),
&TransAffine::new(),
None,
FillRule::NonZero,
label,
);
}
}
#[test]
fn fill_path_bbox_evenodd_self_overlapping() {
let make = || {
let mut p = PathStorage::new();
p.move_to(10.0, 10.0);
p.line_to(70.0, 10.0);
p.line_to(70.0, 50.0);
p.line_to(10.0, 50.0);
p.close_polygon(PATH_FLAGS_NONE);
p.move_to(25.0, 22.0);
p.line_to(55.0, 22.0);
p.line_to(55.0, 38.0);
p.line_to(25.0, 38.0);
p.close_polygon(PATH_FLAGS_NONE);
p
};
assert_fill_equiv(
90,
60,
Some(Color::white()),
make,
Color::rgba(0.15, 0.25, 0.35, 1.0),
&TransAffine::new(),
None,
FillRule::EvenOdd,
"evenodd self-overlapping rects",
);
}
#[test]
fn fill_path_bbox_non_opaque_over_content() {
assert_fill_equiv(
80,
50,
Some(Color::rgba(0.8, 0.6, 0.4, 1.0)),
|| rect(15.4, 12.6, 60.4, 34.6),
Color::rgba(0.0, 0.0, 1.0, 0.5),
&TransAffine::new(),
None,
FillRule::NonZero,
"half-alpha blue over solid content",
);
}