summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Libraries/LibGfx/FloatPoint.h9
-rw-r--r--Libraries/LibGfx/Painter.cpp179
-rw-r--r--Libraries/LibGfx/Painter.h9
-rw-r--r--Libraries/LibGfx/Path.cpp61
-rw-r--r--Libraries/LibGfx/Path.h24
5 files changed, 274 insertions, 8 deletions
diff --git a/Libraries/LibGfx/FloatPoint.h b/Libraries/LibGfx/FloatPoint.h
index 766197d967..9867d8327d 100644
--- a/Libraries/LibGfx/FloatPoint.h
+++ b/Libraries/LibGfx/FloatPoint.h
@@ -37,7 +37,7 @@ class FloatRect;
class FloatPoint {
public:
- FloatPoint() {}
+ FloatPoint() { }
FloatPoint(float x, float y)
: m_x(x)
, m_y(y)
@@ -112,6 +112,13 @@ public:
}
FloatPoint operator+(const FloatPoint& other) const { return { m_x + other.m_x, m_y + other.m_y }; }
+ FloatPoint& operator/=(float factor)
+ {
+ m_x /= factor;
+ m_y /= factor;
+ return *this;
+ }
+
String to_string() const { return String::format("[%g,%g]", x(), y()); }
bool is_null() const { return !m_x && !m_y; }
diff --git a/Libraries/LibGfx/Painter.cpp b/Libraries/LibGfx/Painter.cpp
index 85c21d0121..549070b107 100644
--- a/Libraries/LibGfx/Painter.cpp
+++ b/Libraries/LibGfx/Painter.cpp
@@ -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
+}
+
}
diff --git a/Libraries/LibGfx/Painter.h b/Libraries/LibGfx/Painter.h
index 97fe226eeb..87ecf8b045 100644
--- a/Libraries/LibGfx/Painter.h
+++ b/Libraries/LibGfx/Painter.h
@@ -71,8 +71,17 @@ public:
void draw_emoji(const Point&, const Gfx::Bitmap&, const Font&);
void draw_glyph_or_emoji(const Point&, u32 codepoint, const Font&, Color);
+ static void for_each_line_segment_on_bezier_curve(const FloatPoint& control_point, const FloatPoint& p1, const FloatPoint& p2, Function<void(const FloatPoint&, const FloatPoint&)>&);
+ static void for_each_line_segment_on_bezier_curve(const FloatPoint& control_point, const FloatPoint& p1, const FloatPoint& p2, Function<void(const FloatPoint&, const FloatPoint&)>&&);
+
void stroke_path(const Path&, Color, int thickness);
+ enum class WindingRule {
+ Nonzero,
+ EvenOdd,
+ };
+ void fill_path(Path&, Color, WindingRule rule = WindingRule::Nonzero);
+
const Font& font() const { return *state().font; }
void set_font(const Font& font) { state().font = &font; }
diff --git a/Libraries/LibGfx/Path.cpp b/Libraries/LibGfx/Path.cpp
index fa06b904f7..2bf990d6fe 100644
--- a/Libraries/LibGfx/Path.cpp
+++ b/Libraries/LibGfx/Path.cpp
@@ -24,7 +24,10 @@
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
+#include <AK/Function.h>
+#include <AK/QuickSort.h>
#include <AK/StringBuilder.h>
+#include <LibGfx/Painter.h>
#include <LibGfx/Path.h>
namespace Gfx {
@@ -34,6 +37,8 @@ void Path::close()
if (m_segments.size() <= 1)
return;
+ invalidate_split_lines();
+
auto& last_point = m_segments.last().point;
for (ssize_t i = m_segments.size() - 1; i >= 0; --i) {
@@ -80,4 +85,60 @@ String Path::to_string() const
return builder.to_string();
}
+void Path::segmentize_path()
+{
+ Vector<LineSegment> segments;
+
+ auto add_line = [&](const auto& p0, const auto& p1) {
+ if (p0.y() == p1.y())
+ return; // horizontal lines are not needed (there's nothing to fill inside)
+ float ymax = p0.y(), ymin = p1.y(), x_of_ymin = p1.x(), x_of_ymax = p0.x();
+ auto slope = p0.x() == p1.x() ? 0 : ((float)(p0.y() - p1.y())) / ((float)(p0.x() - p1.x()));
+ if (p0.y() < p1.y()) {
+ ymin = ymax;
+ ymax = p1.y();
+ x_of_ymax = x_of_ymin;
+ x_of_ymin = p0.x();
+ }
+
+ segments.append({ Point(p0.x(), p0.y()),
+ Point(p1.x(), p1.y()),
+ slope == 0 ? 0 : 1 / slope,
+ x_of_ymin,
+ ymax, ymin, x_of_ymax });
+ };
+
+ FloatPoint cursor { 0, 0 };
+ for (auto& segment : m_segments) {
+ switch (segment.type) {
+ case Segment::Type::MoveTo:
+ cursor = segment.point;
+ break;
+ case Segment::Type::LineTo: {
+ add_line(cursor, segment.point);
+ cursor = segment.point;
+ break;
+ }
+ case Segment::Type::QuadraticBezierCurveTo: {
+ auto& control = segment.through.value();
+ Painter::for_each_line_segment_on_bezier_curve(control, cursor, segment.point, [&](const FloatPoint& p0, const FloatPoint& p1) {
+ add_line(Point(p0.x(), p0.y()), Point(p1.x(), p1.y()));
+ });
+ cursor = segment.point;
+ break;
+ }
+ case Segment::Type::Invalid:
+ ASSERT_NOT_REACHED();
+ break;
+ }
+ }
+
+ // sort segments by ymax
+ quick_sort(segments, [](const auto& line0, const auto& line1) {
+ return line1.maximum_y < line0.maximum_y;
+ });
+
+ m_split_lines = move(segments);
+}
+
}
diff --git a/Libraries/LibGfx/Path.h b/Libraries/LibGfx/Path.h
index 43f773a819..df4c2d25a4 100644
--- a/Libraries/LibGfx/Path.h
+++ b/Libraries/LibGfx/Path.h
@@ -57,21 +57,45 @@ public:
void line_to(const FloatPoint& point)
{
m_segments.append({ Segment::Type::LineTo, point });
+ invalidate_split_lines();
}
void quadratic_bezier_curve_to(const FloatPoint& through, const FloatPoint& point)
{
m_segments.append({ Segment::Type::QuadraticBezierCurveTo, point, through });
+ invalidate_split_lines();
}
void close();
const Vector<Segment>& segments() const { return m_segments; }
+ const auto& split_lines()
+ {
+ if (m_split_lines.has_value())
+ return m_split_lines.value();
+ segmentize_path();
+ ASSERT(m_split_lines.has_value());
+ return m_split_lines.value();
+ }
String to_string() const;
+ struct LineSegment {
+ Point from, to;
+ float inverse_slope;
+ float x_of_minimum_y;
+ float maximum_y;
+ float minimum_y;
+ float x;
+ };
+
private:
+ void invalidate_split_lines() { m_split_lines.clear(); }
+ void segmentize_path();
+
Vector<Segment> m_segments;
+
+ Optional<Vector<LineSegment>> m_split_lines {};
};
inline const LogStream& operator<<(const LogStream& stream, const Path& path)