1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-18 08:45:06 +00:00

LibGfx: Implement filling paths

There are some imperfections with intersecting edges (because the main
algorithm used is scanline, and that is not geared towards drawing
complex shapes), however, it behaves mostly fine for normal use :^)
This commit is contained in:
AnotherTest 2020-05-06 11:55:12 +04:30 committed by Andreas Kling
parent 4d20cf57db
commit f54b41f748
5 changed files with 274 additions and 8 deletions

View file

@ -31,6 +31,7 @@
#include <AK/Assertions.h>
#include <AK/Function.h>
#include <AK/Memory.h>
#include <AK/QuickSort.h>
#include <AK/StdLibExtras.h>
#include <AK/StringBuilder.h>
#include <AK/Utf8View.h>
@ -1049,7 +1050,7 @@ void Painter::draw_line(const Point& p1, const Point& p2, Color color, int thick
}
}
static void draw_split_quadratic_bezier_curve(Painter& painter, const Point& original_control, const Point& p1, const Point& p2, Color color, int thickness, bool dotted)
static void split_quadratic_bezier_curve(const FloatPoint& original_control, const FloatPoint& p1, const FloatPoint& p2, Function<void(const FloatPoint&, const FloatPoint&)>& callback)
{
auto po1_midpoint = original_control + p1;
po1_midpoint /= 2;
@ -1060,11 +1061,11 @@ static void draw_split_quadratic_bezier_curve(Painter& painter, const Point& ori
auto new_segment = po1_midpoint + po2_midpoint;
new_segment /= 2;
painter.draw_quadratic_bezier_curve(po1_midpoint, p1, new_segment, color, thickness, dotted);
painter.draw_quadratic_bezier_curve(po2_midpoint, new_segment, p2, color, thickness, dotted);
Painter::for_each_line_segment_on_bezier_curve(po1_midpoint, p1, new_segment, callback);
Painter::for_each_line_segment_on_bezier_curve(po2_midpoint, new_segment, p2, callback);
}
static bool can_approximate_bezier_curve(const Point& p1, const Point& p2, const Point& control)
static bool can_approximate_bezier_curve(const FloatPoint& p1, const FloatPoint& p2, const FloatPoint& control)
{
constexpr static int tolerance = 15;
@ -1081,15 +1082,27 @@ static bool can_approximate_bezier_curve(const Point& p1, const Point& p2, const
return max(p1x, p2x) + max(p1y, p2y) <= tolerance;
}
void Painter::draw_quadratic_bezier_curve(const Point& control_point, const Point& p1, const Point& p2, Color color, int thickness, bool dotted)
void Painter::for_each_line_segment_on_bezier_curve(const FloatPoint& control_point, const FloatPoint& p1, const FloatPoint& p2, Function<void(const FloatPoint&, const FloatPoint&)>& callback)
{
if (can_approximate_bezier_curve(p1, p2, control_point)) {
draw_line(p1, p2, color, thickness, dotted);
callback(p1, p2);
} else {
draw_split_quadratic_bezier_curve(*this, control_point, p1, p2, color, thickness, dotted);
split_quadratic_bezier_curve(control_point, p1, p2, callback);
}
}
void Painter::for_each_line_segment_on_bezier_curve(const FloatPoint& control_point, const FloatPoint& p1, const FloatPoint& p2, Function<void(const FloatPoint&, const FloatPoint&)>&& callback)
{
for_each_line_segment_on_bezier_curve(control_point, p1, p2, callback);
}
void Painter::draw_quadratic_bezier_curve(const Point& control_point, const Point& p1, const Point& p2, Color color, int thickness, bool dotted)
{
for_each_line_segment_on_bezier_curve(FloatPoint(control_point.x(), control_point.y()), FloatPoint(p1.x(), p1.y()), FloatPoint(p2.x(), p2.y()), [&](const FloatPoint& p1, const FloatPoint& p2) {
draw_line(Point(p1.x(), p1.y()), Point(p2.x(), p2.y()), color, thickness, dotted);
});
}
void Painter::add_clip_rect(const Rect& rect)
{
state().clip_rect.intersect(rect.translated(m_clip_origin.location()));
@ -1137,4 +1150,156 @@ void Painter::stroke_path(const Path& path, Color color, int thickness)
}
}
//#define FILL_PATH_DEBUG
void Painter::fill_path(Path& path, Color color, WindingRule winding_rule)
{
const auto& segments = path.split_lines();
if (segments.size() == 0)
return;
Vector<Path::LineSegment> active_list;
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;
size_t last_active_segment { 0 };
for (auto& segment : segments) {
if (segment.maximum_y != scanline)
break;
active_list.append(segment);
++last_active_segment;
}
auto is_inside_shape = [winding_rule](int winding_number) {
if (winding_rule == WindingRule::Nonzero)
return winding_number != 0;
if (winding_rule == WindingRule::EvenOdd)
return winding_number % 2 == 0;
ASSERT_NOT_REACHED();
};
auto increment_winding = [winding_rule](int& winding_number, const Point& from, const Point& to) {
if (winding_rule == WindingRule::EvenOdd) {
++winding_number;
return;
}
if (winding_rule == WindingRule::Nonzero) {
if (from.dy_relative_to(to) < 0)
++winding_number;
else
--winding_number;
return;
}
ASSERT_NOT_REACHED();
};
while (scanline >= last_y) {
if (active_list.size()) {
// sort the active list by 'x' from right to left
quick_sort(active_list, [](const auto& line0, const auto& line1) {
return line1.x < line0.x;
});
#ifdef FILL_PATH_DEBUG
if ((int)scanline % 10 == 0) {
draw_text(Rect(active_list.last().x - 20, scanline, 20, 10), String::format("%d", (int)scanline));
}
#endif
if (active_list.size() > 1) {
auto winding_number { 0 };
for (size_t i = 1; i < active_list.size(); ++i) {
auto& previous = active_list[i - 1];
auto& current = active_list[i];
int int_distance = fabs(current.x - previous.x);
Point from(previous.x, scanline);
Point 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;
}
if (is_inside_shape(winding_number)) {
// The points between this segment and the previous are
// inside the shape
#ifdef FILL_PATH_DEBUG
dbg() << "y=" << scanline << ": " << winding_number << " at " << i << ": " << from << " -- " << to;
#endif
draw_line(from, to, color, 1, false);
}
skip_drawing:;
auto is_passing_through_maxima = scanline == previous.maximum_y
|| scanline == previous.minimum_y
|| scanline == current.maximum_y
|| scanline == current.minimum_y;
auto is_passing_through_vertex = false;
if (is_passing_through_maxima) {
is_passing_through_vertex = previous.x == current.x;
}
if (!is_passing_through_vertex || previous.inverse_slope * current.inverse_slope < 0)
increment_winding(winding_number, from, to);
// update the x coord
active_list[i - 1].x -= active_list[i - 1].inverse_slope;
}
active_list.last().x -= active_list.last().inverse_slope;
} else {
auto point = Point(active_list[0].x, scanline);
draw_line(point, point, color);
// update the x coord
active_list.first().x -= active_list.first().inverse_slope;
}
}
--scanline;
// remove any edge that goes out of bound from the active list
for (size_t i = 0, count = active_list.size(); i < count; ++i) {
if (scanline <= active_list[i].minimum_y) {
active_list.remove(i);
--count;
--i;
}
}
for (size_t j = last_active_segment; j < segments.size(); ++j, ++last_active_segment) {
auto& segment = segments[j];
if (segment.maximum_y < scanline)
break;
if (segment.minimum_y >= scanline)
continue;
active_list.append(segment);
}
}
#ifdef FILL_PATH_DEBUG
size_t i { 0 };
for (auto& segment : segments)
draw_line(segment.from, segment.to, Color::from_hsv(++i / segments.size() * 255, 255, 255), 1);
#endif
}
}