mirror of
https://github.com/SerenityOS/serenity.git
synced 2025-01-26 19:32:06 -05:00
LibGfx: Make fill_path() less bad at filling paths
This patchset fixes: - Some parts of the path being skipped and not drawn (often horizontal) - The filled shape moving around on an int grid depending on the winding number - Winding number mess-up with four-way intersections
This commit is contained in:
parent
af80d71abc
commit
0918d8b1f8
1 changed files with 32 additions and 22 deletions
|
@ -1449,6 +1449,28 @@ void Painter::stroke_path(const Path& path, Color color, int thickness)
|
|||
|
||||
//#define FILL_PATH_DEBUG
|
||||
|
||||
[[maybe_unused]] static void approximately_place_on_int_grid(FloatPoint ffrom, FloatPoint fto, IntPoint& from, IntPoint& to, Optional<IntPoint> previous_to)
|
||||
{
|
||||
auto diffs = fto - ffrom;
|
||||
// Truncate all first (round down).
|
||||
from = ffrom.to_type<int>();
|
||||
to = fto.to_type<int>();
|
||||
// There are 16 possible configurations, by deciding to round each
|
||||
// coord up or down (and there are four coords, from.x from.y to.x to.y)
|
||||
// we will simply choose one which most closely matches the correct slope
|
||||
// with the following heuristic:
|
||||
// - if the x diff is positive or zero (that is, a right-to-left slant), round 'from.x' up and 'to.x' down.
|
||||
// - if the x diff is negative (that is, a left-to-right slant), round 'from.x' down and 'to.x' up.
|
||||
// Note that we do not need to touch the 'y' attribute, as that is our scanline.
|
||||
if (diffs.x() >= 0) {
|
||||
from.set_x(from.x() + 1);
|
||||
} else {
|
||||
to.set_x(to.x() + 1);
|
||||
}
|
||||
if (previous_to.has_value() && from.x() != previous_to.value().x()) // The points have to line up, since we're using these lines to fill a shape.
|
||||
from.set_x(previous_to.value().x());
|
||||
}
|
||||
|
||||
void Painter::fill_path(Path& path, Color color, WindingRule winding_rule)
|
||||
{
|
||||
const auto& segments = path.split_lines();
|
||||
|
@ -1460,9 +1482,9 @@ void Painter::fill_path(Path& path, Color color, WindingRule winding_rule)
|
|||
active_list.ensure_capacity(segments.size());
|
||||
|
||||
// first, grab the segments for the very first scanline
|
||||
auto first_y = segments.first().maximum_y;
|
||||
auto last_y = segments.last().minimum_y;
|
||||
auto scanline = first_y;
|
||||
int first_y = path.bounding_box().bottom_right().y() + 1;
|
||||
int last_y = path.bounding_box().top_left().y() - 1;
|
||||
float scanline = first_y;
|
||||
|
||||
size_t last_active_segment { 0 };
|
||||
|
||||
|
@ -1501,6 +1523,7 @@ void Painter::fill_path(Path& path, Color color, WindingRule winding_rule)
|
|||
};
|
||||
|
||||
while (scanline >= last_y) {
|
||||
Optional<IntPoint> previous_to;
|
||||
if (active_list.size()) {
|
||||
// sort the active list by 'x' from right to left
|
||||
quick_sort(active_list, [](const auto& line0, const auto& line1) {
|
||||
|
@ -1518,21 +1541,10 @@ void Painter::fill_path(Path& path, Color color, WindingRule winding_rule)
|
|||
auto& previous = active_list[i - 1];
|
||||
auto& current = active_list[i];
|
||||
|
||||
int int_distance = fabs(current.x - previous.x);
|
||||
IntPoint from(previous.x, scanline);
|
||||
IntPoint to(current.x, scanline);
|
||||
|
||||
if (int_distance < 1) {
|
||||
// the two lines intersect on an int grid
|
||||
// so they should both be treated as a single line segment
|
||||
goto skip_drawing;
|
||||
}
|
||||
|
||||
if (int_distance == 1 && is_inside_shape(winding_number)) {
|
||||
// The two lines form a singluar edge for the shape
|
||||
// while they do not intersect, they connect together
|
||||
goto skip_drawing;
|
||||
}
|
||||
IntPoint from, to;
|
||||
IntPoint truncated_from { previous.x, scanline };
|
||||
IntPoint truncated_to { current.x, scanline };
|
||||
approximately_place_on_int_grid({ previous.x, scanline }, { current.x, scanline }, from, to, previous_to);
|
||||
|
||||
if (is_inside_shape(winding_number)) {
|
||||
// The points between this segment and the previous are
|
||||
|
@ -1543,8 +1555,6 @@ void Painter::fill_path(Path& path, Color color, WindingRule winding_rule)
|
|||
draw_line(from, to, color, 1);
|
||||
}
|
||||
|
||||
skip_drawing:;
|
||||
|
||||
auto is_passing_through_maxima = scanline == previous.maximum_y
|
||||
|| scanline == previous.minimum_y
|
||||
|| scanline == current.maximum_y
|
||||
|
@ -1557,7 +1567,7 @@ void Painter::fill_path(Path& path, Color color, WindingRule winding_rule)
|
|||
}
|
||||
|
||||
if (!is_passing_through_vertex || previous.inverse_slope * current.inverse_slope < 0)
|
||||
increment_winding(winding_number, from, to);
|
||||
increment_winding(winding_number, truncated_from, truncated_to);
|
||||
|
||||
// update the x coord
|
||||
active_list[i - 1].x -= active_list[i - 1].inverse_slope;
|
||||
|
@ -1595,7 +1605,7 @@ void Painter::fill_path(Path& path, Color color, WindingRule winding_rule)
|
|||
#ifdef FILL_PATH_DEBUG
|
||||
size_t i { 0 };
|
||||
for (auto& segment : segments) {
|
||||
draw_line(Point<int>(segment.from), Point<int>(segment.to), Color::from_hsv(++i / segments.size() * 360.0, 1.0, 1.0), 1);
|
||||
draw_line(Point<int>(segment.from), Point<int>(segment.to), Color::from_hsv(i++ * 360.0 / segments.size(), 1.0, 1.0), 1);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue