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:
parent
4d20cf57db
commit
f54b41f748
5 changed files with 274 additions and 8 deletions
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue