LibGfx: Revert #2154 and properly handle simple polygons

The existing scanline method works just fine, and only needs the points
to be available as floats.
This commit reverts the complex polygon mitigation, and instead fixes
the rasterization process to avoid generating complex polygons because
of precision issues.
This commit is contained in:
AnotherTest 2020-05-08 21:22:38 +04:30 committed by Andreas Kling
parent 0f0d73d1b5
commit 88738aefa3
Notes: sideshowbarker 2024-07-19 06:53:08 +09:00
3 changed files with 5 additions and 187 deletions

View file

@ -1154,7 +1154,7 @@ void Painter::stroke_path(const Path& path, Color color, int thickness)
void Painter::fill_path(Path& path, Color color, WindingRule winding_rule)
{
const auto& segments = path.split_lines(Path::Simple);
const auto& segments = path.split_lines();
if (segments.size() == 0)
return;

View file

@ -102,8 +102,8 @@ void Path::segmentize_path()
x_of_ymin = p0.x();
}
segments.append({ Point(p0.x(), p0.y()),
Point(p1.x(), p1.y()),
segments.append({ FloatPoint(p0.x(), p0.y()),
FloatPoint(p1.x(), p1.y()),
slope == 0 ? 0 : 1 / slope,
x_of_ymin,
ymax, ymin, x_of_ymax });
@ -123,7 +123,7 @@ void Path::segmentize_path()
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()));
add_line(p0, p1);
});
cursor = segment.point;
break;
@ -142,162 +142,4 @@ void Path::segmentize_path()
m_split_lines = move(segments);
}
Vector<Path::LineSegment> Path::split_lines(Path::ShapeKind kind)
{
if (m_split_lines.has_value()) {
const auto& lines = m_split_lines.value();
if (kind == Complex)
return lines;
Vector<LineSegment> segments;
for (auto& line : lines) {
if (is_part_of_closed_polygon(line.from, line.to))
segments.append(line);
}
return move(segments);
}
segmentize_path();
ASSERT(m_split_lines.has_value());
return split_lines(kind);
}
void Path::generate_path_graph()
{
// Generate a (possibly) disconnected cyclic directed graph
// of the line segments in the path.
// This graph will be used to determine whether a line should
// be considered as part of an edge for the shape
// FIXME: This will not chop lines up, so we might still have some
// filling artifacts after this, as a line might pass over an edge
// but be itself a part of _another_ polygon.
HashMap<u32, OwnPtr<PathGraphNode>> graph;
m_graph_node_map = move(graph);
const auto& lines = split_lines();
if (!lines.size())
return;
// now use scanline to find intersecting lines
auto scanline = lines.first().maximum_y;
auto last_line = lines.last().minimum_y;
Vector<LineSegment> active_list;
for (auto& line : lines) {
if (line.maximum_y < scanline)
break;
active_list.append(line);
}
while (scanline >= last_line) {
if (active_list.size() > 1) {
quick_sort(active_list, [](const auto& line0, const auto& line1) {
return line1.x < line0.x;
});
// for every two lines next to each other in the active list
// figure out if they intersect, if they do, store
// the right line as the child of the left line
// in the path graph
for (size_t i = 1; i < active_list.size(); ++i) {
auto& left_line = active_list[i - 1];
auto& right_line = active_list[i];
auto left_hash = hash_line(left_line.from, left_line.to);
auto right_hash = hash_line(right_line.from, right_line.to);
auto maybe_left_entry = m_graph_node_map.value().get(left_hash);
auto maybe_right_entry = m_graph_node_map.value().get(right_hash);
if (!maybe_left_entry.has_value()) {
auto left_entry = make<PathGraphNode>(left_hash, left_line);
m_graph_node_map.value().set(left_hash, move(left_entry));
maybe_left_entry = m_graph_node_map.value().get(left_hash);
}
if (!maybe_right_entry.has_value()) {
auto right_entry = make<PathGraphNode>(right_hash, right_line);
m_graph_node_map.value().set(right_hash, move(right_entry));
maybe_right_entry = m_graph_node_map.value().get(right_hash);
}
// check all four sides for possible intersection
if (((int)fabs(left_line.x - right_line.x)) <= 1
|| ((int)fabs(left_line.x - right_line.x + left_line.inverse_slope)) <= 1
|| ((int)fabs(left_line.x - right_line.x + right_line.inverse_slope)) <= 1
|| ((int)fabs(left_line.x - right_line.x + +right_line.inverse_slope + left_line.inverse_slope)) <= 1) {
const_cast<PathGraphNode*>(maybe_left_entry.value())->children.append(maybe_right_entry.value());
}
left_line.x -= left_line.inverse_slope;
}
active_list.last().x -= active_list.last().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;
}
}
}
}
bool Path::is_part_of_closed_polygon(const Point& p0, const Point& p1)
{
if (!m_graph_node_map.has_value())
generate_path_graph();
ASSERT(m_graph_node_map.has_value());
auto hash = hash_line(p0, p1);
auto maybe_entry = m_graph_node_map.value().get(hash);
if (!maybe_entry.has_value())
return true;
const auto& entry = maybe_entry.value();
// check if the entry is part of a loop
auto is_part_of_loop = false;
HashTable<u32> visited;
Vector<const PathGraphNode*> queue;
queue.append(entry);
for (; queue.size();) {
const auto* node = queue.take_first();
if (visited.contains(node->hash))
continue;
visited.set(node->hash);
if (node == entry) {
is_part_of_loop = true;
break;
}
}
return is_part_of_loop;
}
// FIXME: We need a better hash, and a wider type
unsigned Path::hash_line(const Point& from, const Point& to)
{
u32 p0 = pair_int_hash(from.x(), from.y());
u32 p1 = pair_int_hash(to.x(), to.y());
return pair_int_hash(p0, p1);
}
}

View file

@ -71,7 +71,7 @@ public:
void close();
struct LineSegment {
Point from, to;
FloatPoint from, to;
float inverse_slope;
float x_of_minimum_y;
float maximum_y;
@ -79,13 +79,7 @@ public:
float x;
};
enum ShapeKind {
Simple,
Complex,
};
const Vector<Segment>& segments() const { return m_segments; }
Vector<LineSegment> split_lines(ShapeKind);
const auto& split_lines()
{
if (m_split_lines.has_value())
@ -101,30 +95,12 @@ private:
void invalidate_split_lines()
{
m_split_lines.clear();
m_graph_node_map.clear();
}
void segmentize_path();
void generate_path_graph();
bool is_part_of_closed_polygon(const Point& p0, const Point& p1);
static unsigned hash_line(const Point& from, const Point& to);
Vector<Segment> m_segments;
Optional<Vector<LineSegment>> m_split_lines {};
struct PathGraphNode {
PathGraphNode(u32 hash, const LineSegment& line)
: hash(hash)
, line(line)
{
}
Vector<const PathGraphNode*> children;
u32 hash;
LineSegment line;
};
Optional<HashMap<u32, OwnPtr<PathGraphNode>>> m_graph_node_map;
};
inline const LogStream& operator<<(const LogStream& stream, const Path& path)