Skip to content

Commit 74a6a3d

Browse files
MacDuenico
authored andcommitted
LibGfx: Limit curve split depth based on approximate curve length
This limits the subdivision of the curve to segments with length of at least ~0.5px. This prevents us from getting stuck endlessly splitting, and could also reduce segment count in some cases. Fixes #23620
1 parent 7b54fe6 commit 74a6a3d

File tree

1 file changed

+51
-30
lines changed

1 file changed

+51
-30
lines changed

Userland/Libraries/LibGfx/Painter.cpp

+51-30
Original file line numberDiff line numberDiff line change
@@ -2106,38 +2106,49 @@ static bool can_approximate_bezier_curve(FloatPoint p1, FloatPoint p2, FloatPoin
21062106
return error <= tolerance;
21072107
}
21082108

2109+
static float approximate_bezier_curve_length(FloatPoint control_point, FloatPoint p1, FloatPoint p2)
2110+
{
2111+
return p1.distance_from(control_point) + control_point.distance_from(p2);
2112+
}
2113+
21092114
// static
21102115
void Painter::for_each_line_segment_on_bezier_curve(FloatPoint control_point, FloatPoint p1, FloatPoint p2, Function<void(FloatPoint, FloatPoint)>& callback)
21112116
{
21122117
struct SegmentDescriptor {
21132118
FloatPoint control_point;
21142119
FloatPoint p1;
21152120
FloatPoint p2;
2116-
};
2121+
int depth { 0 };
21172122

2118-
static constexpr auto split_quadratic_bezier_curve = [](FloatPoint original_control, FloatPoint p1, FloatPoint p2, auto& segments) {
2119-
auto po1_midpoint = original_control + p1;
2120-
po1_midpoint /= 2;
2123+
void split(Vector<SegmentDescriptor>& segments)
2124+
{
2125+
auto po1_midpoint = control_point + p1;
2126+
po1_midpoint /= 2;
21212127

2122-
auto po2_midpoint = original_control + p2;
2123-
po2_midpoint /= 2;
2128+
auto po2_midpoint = control_point + p2;
2129+
po2_midpoint /= 2;
21242130

2125-
auto new_segment = po1_midpoint + po2_midpoint;
2126-
new_segment /= 2;
2131+
auto new_segment = po1_midpoint + po2_midpoint;
2132+
new_segment /= 2;
21272133

2128-
segments.append({ po2_midpoint, new_segment, p2 });
2129-
segments.append({ po1_midpoint, p1, new_segment });
2134+
segments.append({ po2_midpoint, new_segment, p2, depth + 1 });
2135+
segments.append({ po1_midpoint, p1, new_segment, depth + 1 });
2136+
}
21302137
};
21312138

2139+
// Limit splitting to the point where curves are approximately half a pixel in length.
2140+
int max_split_depth = ceilf(log2(approximate_bezier_curve_length(control_point, p1, p2))) + 1;
2141+
21322142
Vector<SegmentDescriptor> segments;
21332143
segments.append({ control_point, p1, p2 });
21342144
while (!segments.is_empty()) {
21352145
auto segment = segments.take_last();
21362146

2137-
if (can_approximate_bezier_curve(segment.p1, segment.p2, segment.control_point))
2147+
if (segment.depth >= max_split_depth
2148+
|| can_approximate_bezier_curve(segment.p1, segment.p2, segment.control_point))
21382149
callback(segment.p1, segment.p2);
21392150
else
2140-
split_quadratic_bezier_curve(segment.control_point, segment.p1, segment.p2, segments);
2151+
segment.split(segments);
21412152
}
21422153
}
21432154

@@ -2184,6 +2195,11 @@ static bool can_approximate_cubic_bezier_curve(FloatPoint p1, FloatPoint p2, Flo
21842195
return error <= tolerance;
21852196
}
21862197

2198+
static float approximate_cubic_bezier_curve_length(FloatPoint control_point_0, FloatPoint control_point_1, FloatPoint p1, FloatPoint p2)
2199+
{
2200+
return p1.distance_from(control_point_0) + control_point_0.distance_from(control_point_1) + control_point_1.distance_from(p2);
2201+
}
2202+
21872203
// static
21882204
void Painter::for_each_line_segment_on_cubic_bezier_curve(FloatPoint control_point_0, FloatPoint control_point_1, FloatPoint p1, FloatPoint p2, Function<void(FloatPoint, FloatPoint)>& callback)
21892205
{
@@ -2195,33 +2211,38 @@ void Painter::for_each_line_segment_on_cubic_bezier_curve(FloatPoint control_poi
21952211
ControlPair control_points;
21962212
FloatPoint p1;
21972213
FloatPoint p2;
2198-
};
2214+
int depth { 0 };
21992215

2200-
static constexpr auto split_cubic_bezier_curve = [](ControlPair const& original_controls, FloatPoint p1, FloatPoint p2, auto& segments) {
2201-
Array level_1_midpoints {
2202-
(p1 + original_controls.control_point_0) / 2,
2203-
(original_controls.control_point_0 + original_controls.control_point_1) / 2,
2204-
(original_controls.control_point_1 + p2) / 2,
2205-
};
2206-
Array level_2_midpoints {
2207-
(level_1_midpoints[0] + level_1_midpoints[1]) / 2,
2208-
(level_1_midpoints[1] + level_1_midpoints[2]) / 2,
2209-
};
2210-
auto level_3_midpoint = (level_2_midpoints[0] + level_2_midpoints[1]) / 2;
2211-
2212-
segments.append({ { level_2_midpoints[1], level_1_midpoints[2] }, level_3_midpoint, p2 });
2213-
segments.append({ { level_1_midpoints[0], level_2_midpoints[0] }, p1, level_3_midpoint });
2216+
void split(Vector<SegmentDescriptor>& segments)
2217+
{
2218+
Array level_1_midpoints {
2219+
(p1 + control_points.control_point_0) / 2,
2220+
(control_points.control_point_0 + control_points.control_point_1) / 2,
2221+
(control_points.control_point_1 + p2) / 2,
2222+
};
2223+
Array level_2_midpoints {
2224+
(level_1_midpoints[0] + level_1_midpoints[1]) / 2,
2225+
(level_1_midpoints[1] + level_1_midpoints[2]) / 2,
2226+
};
2227+
auto level_3_midpoint = (level_2_midpoints[0] + level_2_midpoints[1]) / 2;
2228+
2229+
segments.append({ { level_2_midpoints[1], level_1_midpoints[2] }, level_3_midpoint, p2, depth + 1 });
2230+
segments.append({ { level_1_midpoints[0], level_2_midpoints[0] }, p1, level_3_midpoint, depth + 1 });
2231+
}
22142232
};
22152233

2234+
// Limit splitting to the point where curves are approximately half a pixel in length.
2235+
int max_split_depth = ceilf(log2(approximate_cubic_bezier_curve_length(control_point_0, control_point_1, p1, p2))) + 1;
2236+
22162237
Vector<SegmentDescriptor> segments;
22172238
segments.append({ { control_point_0, control_point_1 }, p1, p2 });
22182239
while (!segments.is_empty()) {
22192240
auto segment = segments.take_last();
2220-
2221-
if (can_approximate_cubic_bezier_curve(segment.p1, segment.p2, segment.control_points.control_point_0, segment.control_points.control_point_1))
2241+
if (segment.depth >= max_split_depth
2242+
|| can_approximate_cubic_bezier_curve(segment.p1, segment.p2, segment.control_points.control_point_0, segment.control_points.control_point_1))
22222243
callback(segment.p1, segment.p2);
22232244
else
2224-
split_cubic_bezier_curve(segment.control_points, segment.p1, segment.p2, segments);
2245+
segment.split(segments);
22252246
}
22262247
}
22272248

0 commit comments

Comments
 (0)