Fix drawing over erased Brush tool paths (#3262)

fix draw over erased behaviour

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
snskar 2025-10-15 03:00:57 +05:30 committed by GitHub
parent 497758c273
commit 34d0b76333
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 17 additions and 26 deletions

View File

@ -194,7 +194,6 @@ async fn brush(_: impl Ctx, mut image_frame_table: Table<Raster<CPU>>, strokes:
let background_bounds = bbox.to_transform(); let background_bounds = bbox.to_transform();
let mut draw_strokes: Vec<_> = strokes.iter().filter(|&s| !matches!(s.style.blend_mode, BlendMode::Erase | BlendMode::Restore)).cloned().collect(); let mut draw_strokes: Vec<_> = strokes.iter().filter(|&s| !matches!(s.style.blend_mode, BlendMode::Erase | BlendMode::Restore)).cloned().collect();
let erase_restore_strokes: Vec<_> = strokes.iter().filter(|&s| matches!(s.style.blend_mode, BlendMode::Erase | BlendMode::Restore)).cloned().collect();
let mut brush_plan = cache.compute_brush_plan(table_row, &draw_strokes); let mut brush_plan = cache.compute_brush_plan(table_row, &draw_strokes);
@ -258,8 +257,8 @@ async fn brush(_: impl Ctx, mut image_frame_table: Table<Raster<CPU>>, strokes:
actual_image = blend_with_mode(actual_image, stroke_texture, stroke.style.blend_mode, (stroke.style.color.a() * 100.) as f64); actual_image = blend_with_mode(actual_image, stroke_texture, stroke.style.blend_mode, (stroke.style.color.a() * 100.) as f64);
} }
let has_erase_strokes = strokes.iter().any(|s| s.style.blend_mode == BlendMode::Erase); let has_erase_or_restore_strokes = strokes.iter().any(|s| matches!(s.style.blend_mode, BlendMode::Erase | BlendMode::Restore));
if has_erase_strokes { if has_erase_or_restore_strokes {
let opaque_image = Image::new(bbox.size().x as u32, bbox.size().y as u32, Color::WHITE); let opaque_image = Image::new(bbox.size().x as u32, bbox.size().y as u32, Color::WHITE);
let mut erase_restore_mask = TableRow { let mut erase_restore_mask = TableRow {
element: Raster::new_cpu(opaque_image), element: Raster::new_cpu(opaque_image),
@ -267,7 +266,7 @@ async fn brush(_: impl Ctx, mut image_frame_table: Table<Raster<CPU>>, strokes:
..Default::default() ..Default::default()
}; };
for stroke in erase_restore_strokes { for stroke in strokes {
let mut brush_texture = cache.get_cached_brush(&stroke.style); let mut brush_texture = cache.get_cached_brush(&stroke.style);
if brush_texture.is_none() { if brush_texture.is_none() {
let tex = create_brush_texture(&stroke.style).await; let tex = create_brush_texture(&stroke.style).await;
@ -277,28 +276,20 @@ async fn brush(_: impl Ctx, mut image_frame_table: Table<Raster<CPU>>, strokes:
let brush_texture = brush_texture.unwrap(); let brush_texture = brush_texture.unwrap();
let positions: Vec<_> = stroke.compute_blit_points().into_iter().collect(); let positions: Vec<_> = stroke.compute_blit_points().into_iter().collect();
match stroke.style.blend_mode { // For mask composition: Erase subtracts alpha, Restore adds alpha, and Draw acts like Restore to allow repainting erased areas.
BlendMode::Erase => { let mask_blend_mode = match stroke.style.blend_mode {
let blend_params = FnNode::new(|(a, b)| blend_colors(a, b, BlendMode::Erase, 1.)); BlendMode::Erase => BlendMode::Erase,
let blit_node = BlitNode::new( BlendMode::Restore => BlendMode::Restore,
FutureWrapperNode::new(ClonedNode::new(brush_texture)), _ => BlendMode::Restore,
FutureWrapperNode::new(ClonedNode::new(positions)), };
FutureWrapperNode::new(ClonedNode::new(blend_params)),
); let blend_params = FnNode::new(move |(a, b)| blend_colors(a, b, mask_blend_mode, 1.));
erase_restore_mask = blit_node.eval(Table::new_from_row(erase_restore_mask)).await.into_iter().next().unwrap_or_default(); let blit_node = BlitNode::new(
} FutureWrapperNode::new(ClonedNode::new(brush_texture)),
// Yes, this is essentially the same as the above, but we duplicate to inline the blend mode. FutureWrapperNode::new(ClonedNode::new(positions)),
BlendMode::Restore => { FutureWrapperNode::new(ClonedNode::new(blend_params)),
let blend_params = FnNode::new(|(a, b)| blend_colors(a, b, BlendMode::Restore, 1.)); );
let blit_node = BlitNode::new( erase_restore_mask = blit_node.eval(Table::new_from_row(erase_restore_mask)).await.into_iter().next().unwrap_or_default();
FutureWrapperNode::new(ClonedNode::new(brush_texture)),
FutureWrapperNode::new(ClonedNode::new(positions)),
FutureWrapperNode::new(ClonedNode::new(blend_params)),
);
erase_restore_mask = blit_node.eval(Table::new_from_row(erase_restore_mask)).await.into_iter().next().unwrap_or_default();
}
_ => unreachable!(),
}
} }
let blend_params = FnNode::new(|(a, b)| blend_colors(a, b, BlendMode::MultiplyAlpha, 1.)); let blend_params = FnNode::new(|(a, b)| blend_colors(a, b, BlendMode::MultiplyAlpha, 1.));