mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-24 09:46:59 -06:00
Render: a fill with a dashed brush stroke fades as a whole under object opacity
Review follow-up (CodeRabbit): leaving a dashed brush stroke out of the paint count still let a fill plus that stroke count as one paint, so the fill and each brush piece took the object opacity separately and the fill showed through the brush art. A visible dashed brush stroke now forces the layered path, like a non-solid paint does, and counts normally otherwise. brush_fx::tests::object_opacity_fades_a_brushed_stroke_once compares a brushed rectangle at object opacity 0.5 with the same rectangle in a group at 0.5, with and without a dash and a fill; before the change the fill plus dashed brush case had 1440 differing pixels.
This commit is contained in:
committed by
bflatastic
parent
445550ea29
commit
a880b3eb0a
@@ -168,6 +168,54 @@ mod tests {
|
||||
assert_eq!(again.pixels, img.pixels);
|
||||
}
|
||||
|
||||
/// Object opacity fades a brushed stroke's art as a whole, with or without a dash and a fill:
|
||||
/// the same as the object at full opacity inside a group at that opacity.
|
||||
#[test]
|
||||
fn object_opacity_fades_a_brushed_stroke_once() {
|
||||
for (dashed, fill) in [(false, false), (true, false), (false, true), (true, true)] {
|
||||
let node = |d: &mut Document, opacity: f32| {
|
||||
let id = d.alloc_id();
|
||||
let fill = if fill { Paint::solid(Color::rgb(0.9, 0.1, 0.1)) } else { Paint::None };
|
||||
let mut ap = Appearance::basic(fill, Paint::solid(Color::BLACK), 1.0);
|
||||
let st = ap.stroke_mut().unwrap();
|
||||
st.brush = Some("10 pt. Oval".into());
|
||||
if dashed {
|
||||
st.dash = Some(vectorcraft_doc::Dash { pattern: vec![12.0, 4.0], ..Default::default() });
|
||||
}
|
||||
let mut n = Node::path(id, shapes::rectangle(Rect::new(30.0, 30.0, 170.0, 70.0)), ap);
|
||||
n.opacity = opacity;
|
||||
n
|
||||
};
|
||||
let mut faded = Document::new(200.0, 100.0);
|
||||
let n = node(&mut faded, 0.5);
|
||||
let l = faded.layers[0].id;
|
||||
faded.insert(Some(l), 0, n).unwrap();
|
||||
let mut grouped = Document::new(200.0, 100.0);
|
||||
let n = node(&mut grouped, 1.0);
|
||||
let mut g = Node::group(grouped.alloc_id(), vec![std::sync::Arc::new(n)]);
|
||||
g.opacity = 0.5;
|
||||
let l = grouped.layers[0].id;
|
||||
grouped.insert(Some(l), 0, g).unwrap();
|
||||
let (a, b) = (render(&mut Renderer::new(), &faded), render(&mut Renderer::new(), &grouped));
|
||||
assert!(
|
||||
dark(
|
||||
render(&mut Renderer::new(), &{
|
||||
let mut d = Document::new(200.0, 100.0);
|
||||
let n = node(&mut d, 1.0);
|
||||
let l = d.layers[0].id;
|
||||
d.insert(Some(l), 0, n).unwrap();
|
||||
d
|
||||
})
|
||||
.pixel(30, 50)
|
||||
),
|
||||
"the brush paints the edge"
|
||||
);
|
||||
let off =
|
||||
a.pixels.chunks(4).zip(b.pixels.chunks(4)).filter(|(x, y)| x.iter().zip(*y).any(|(p, q)| (*p as i32 - *q as i32).abs() > 2)).count();
|
||||
assert_eq!(off, 0, "dashed {dashed}, fill {fill}: {off} pixels differ from the object faded as a whole");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unknown_brush_falls_back_to_plain_stroke_and_scatter_renders() {
|
||||
let mut r = Renderer::new();
|
||||
|
||||
@@ -2247,18 +2247,18 @@ fn single_plain_fill(n: &Node) -> bool {
|
||||
|
||||
/// Number of visible painted fill/stroke items (opacity folding is exact only for one). A dashed
|
||||
/// stroke is one paint: its dashes are outlined into one path and filled once, so overlapping
|
||||
/// dashes don't darken where they overlap. A dashed brush stroke isn't: its pieces are drawn one
|
||||
/// by one.
|
||||
/// dashes don't darken where they overlap. A dashed brush stroke draws its pieces one by one, so
|
||||
/// it takes the layered path, with or without other paints.
|
||||
fn painted_items(n: &Node) -> usize {
|
||||
n.appearance
|
||||
.items
|
||||
.iter()
|
||||
.filter(|i| match i {
|
||||
AppearanceItem::Fill(f) => f.visible && !f.paint.is_none() && matches!(f.paint, vectorcraft_color::Paint::Solid { .. }),
|
||||
AppearanceItem::Stroke(s) => s.visible && !s.paint.is_none() && s.width > 0.0 && matches!(s.paint, vectorcraft_color::Paint::Solid { .. }) && (s.dash.is_none() || s.brush.is_none()),
|
||||
AppearanceItem::Stroke(s) => s.visible && !s.paint.is_none() && s.width > 0.0 && matches!(s.paint, vectorcraft_color::Paint::Solid { .. }),
|
||||
})
|
||||
.count()
|
||||
.max(if n.appearance.items.iter().any(|i| matches!(i, AppearanceItem::Fill(f) if f.visible && !matches!(f.paint, vectorcraft_color::Paint::Solid { .. } | vectorcraft_color::Paint::None)) || matches!(i, AppearanceItem::Stroke(s) if s.visible && !matches!(s.paint, vectorcraft_color::Paint::Solid { .. } | vectorcraft_color::Paint::None))) { 2 } else { 0 })
|
||||
.max(if n.appearance.items.iter().any(|i| matches!(i, AppearanceItem::Fill(f) if f.visible && !matches!(f.paint, vectorcraft_color::Paint::Solid { .. } | vectorcraft_color::Paint::None)) || matches!(i, AppearanceItem::Stroke(s) if s.visible && (!matches!(s.paint, vectorcraft_color::Paint::Solid { .. } | vectorcraft_color::Paint::None) || (s.dash.is_some() && s.brush.is_some() && !s.paint.is_none())))) { 2 } else { 0 })
|
||||
}
|
||||
|
||||
/// Whether `st` is one of `n`'s own strokes (the strokes of the brush art drawn for `n` are not).
|
||||
|
||||
Reference in New Issue
Block a user