mirror of
https://github.com/RGBCube/serenity
synced 2025-07-26 08:37:45 +00:00
LibGfx: Implement cubic bezier curves by splitting them to subcurves
This makes them significantly more nicer-looking, and fixes a FIXME :^)
This commit is contained in:
parent
02d949cfb6
commit
433725fef2
6 changed files with 145 additions and 12 deletions
|
@ -132,6 +132,14 @@ void Gfx::AntiAliasingPainter::stroke_path(Path const& path, Color color, float
|
||||||
cursor = segment.point();
|
cursor = segment.point();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case Segment::Type::CubicBezierCurveTo: {
|
||||||
|
auto& curve = static_cast<CubicBezierCurveSegment const&>(segment);
|
||||||
|
auto& through_0 = curve.through_0();
|
||||||
|
auto& through_1 = curve.through_1();
|
||||||
|
draw_cubic_bezier_curve(through_0, through_1, cursor, segment.point(), color, thickness);
|
||||||
|
cursor = segment.point();
|
||||||
|
break;
|
||||||
|
}
|
||||||
case Segment::Type::EllipticalArcTo:
|
case Segment::Type::EllipticalArcTo:
|
||||||
auto& arc = static_cast<EllipticalArcSegment const&>(segment);
|
auto& arc = static_cast<EllipticalArcSegment const&>(segment);
|
||||||
draw_elliptical_arc(cursor, segment.point(), arc.center(), arc.radii(), arc.x_axis_rotation(), arc.theta_1(), arc.theta_delta(), color, thickness);
|
draw_elliptical_arc(cursor, segment.point(), arc.center(), arc.radii(), arc.x_axis_rotation(), arc.theta_1(), arc.theta_delta(), color, thickness);
|
||||||
|
@ -154,3 +162,10 @@ void Gfx::AntiAliasingPainter::draw_quadratic_bezier_curve(FloatPoint const& con
|
||||||
draw_line(fp1, fp2, color, thickness, style);
|
draw_line(fp1, fp2, color, thickness, style);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Gfx::AntiAliasingPainter::draw_cubic_bezier_curve(const FloatPoint& control_point_0, const FloatPoint& control_point_1, const FloatPoint& p1, const FloatPoint& p2, Color color, float thickness, Painter::LineStyle style)
|
||||||
|
{
|
||||||
|
Gfx::Painter::for_each_line_segment_on_cubic_bezier_curve(control_point_0, control_point_1, p1, p2, [&](FloatPoint const& fp1, FloatPoint const& fp2) {
|
||||||
|
draw_line(fp1, fp2, color, thickness, style);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ public:
|
||||||
void fill_path(Path&, Color, Painter::WindingRule rule = Painter::WindingRule::Nonzero);
|
void fill_path(Path&, Color, Painter::WindingRule rule = Painter::WindingRule::Nonzero);
|
||||||
void stroke_path(Path const&, Color, float thickness);
|
void stroke_path(Path const&, Color, float thickness);
|
||||||
void draw_quadratic_bezier_curve(FloatPoint const& control_point, FloatPoint const&, FloatPoint const&, Color, float thickness = 1, Painter::LineStyle style = Painter::LineStyle::Solid);
|
void draw_quadratic_bezier_curve(FloatPoint const& control_point, FloatPoint const&, FloatPoint const&, Color, float thickness = 1, Painter::LineStyle style = Painter::LineStyle::Solid);
|
||||||
|
void draw_cubic_bezier_curve(FloatPoint const& control_point_0, FloatPoint const& control_point_1, FloatPoint const&, FloatPoint const&, Color, float thickness = 1, Painter::LineStyle style = Painter::LineStyle::Solid);
|
||||||
void draw_elliptical_arc(FloatPoint const& p1, FloatPoint const& p2, FloatPoint const& center, FloatPoint const& radii, float x_axis_rotation, float theta_1, float theta_delta, Color, float thickness = 1, Painter::LineStyle style = Painter::LineStyle::Solid);
|
void draw_elliptical_arc(FloatPoint const& p1, FloatPoint const& p2, FloatPoint const& center, FloatPoint const& radii, float x_axis_rotation, float theta_1, float theta_delta, Color, float thickness = 1, Painter::LineStyle style = Painter::LineStyle::Solid);
|
||||||
|
|
||||||
void translate(float dx, float dy) { m_transform.translate(dx, dy); }
|
void translate(float dx, float dy) { m_transform.translate(dx, dy); }
|
||||||
|
|
|
@ -1858,6 +1858,76 @@ void Painter::draw_quadratic_bezier_curve(IntPoint const& control_point, IntPoin
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Painter::for_each_line_segment_on_cubic_bezier_curve(FloatPoint const& control_point_0, FloatPoint const& control_point_1, FloatPoint const& p1, FloatPoint const& p2, Function<void(FloatPoint const&, FloatPoint const&)>&& callback)
|
||||||
|
{
|
||||||
|
for_each_line_segment_on_cubic_bezier_curve(control_point_0, control_point_1, p1, p2, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool can_approximate_cubic_bezier_curve(FloatPoint const& p1, FloatPoint const& p2, FloatPoint const& control_0, FloatPoint const& control_1)
|
||||||
|
{
|
||||||
|
constexpr float tolerance = 15; // Arbitrary, seems like 10-30 produces nice results.
|
||||||
|
|
||||||
|
auto ax = 3 * control_0.x() - 2 * p1.x() - p2.x();
|
||||||
|
auto ay = 3 * control_0.y() - 2 * p1.y() - p2.y();
|
||||||
|
auto bx = 3 * control_1.x() - p1.x() - 2 * p2.x();
|
||||||
|
auto by = 3 * control_1.y() - p1.y() - 2 * p2.y();
|
||||||
|
|
||||||
|
ax *= ax;
|
||||||
|
ay *= ay;
|
||||||
|
bx *= bx;
|
||||||
|
by *= by;
|
||||||
|
|
||||||
|
return max(ax, bx) + max(ay, by) <= tolerance;
|
||||||
|
}
|
||||||
|
|
||||||
|
// static
|
||||||
|
void Painter::for_each_line_segment_on_cubic_bezier_curve(FloatPoint const& control_point_0, FloatPoint const& control_point_1, FloatPoint const& p1, FloatPoint const& p2, Function<void(FloatPoint const&, FloatPoint const&)>& callback)
|
||||||
|
{
|
||||||
|
struct ControlPair {
|
||||||
|
FloatPoint control_point_0;
|
||||||
|
FloatPoint control_point_1;
|
||||||
|
};
|
||||||
|
struct SegmentDescriptor {
|
||||||
|
ControlPair control_points;
|
||||||
|
FloatPoint p1;
|
||||||
|
FloatPoint p2;
|
||||||
|
};
|
||||||
|
|
||||||
|
static constexpr auto split_cubic_bezier_curve = [](ControlPair const& original_controls, FloatPoint const& p1, FloatPoint const& p2, auto& segments) {
|
||||||
|
Array level_1_midpoints {
|
||||||
|
(p1 + original_controls.control_point_0) / 2,
|
||||||
|
(original_controls.control_point_0 + original_controls.control_point_1) / 2,
|
||||||
|
(original_controls.control_point_1 + p2) / 2,
|
||||||
|
};
|
||||||
|
Array level_2_midpoints {
|
||||||
|
(level_1_midpoints[0] + level_1_midpoints[1]) / 2,
|
||||||
|
(level_1_midpoints[1] + level_1_midpoints[2]) / 2,
|
||||||
|
};
|
||||||
|
auto level_3_midpoint = (level_2_midpoints[0] + level_2_midpoints[1]) / 2;
|
||||||
|
|
||||||
|
segments.enqueue({ { level_1_midpoints[0], level_2_midpoints[0] }, p1, level_3_midpoint });
|
||||||
|
segments.enqueue({ { level_2_midpoints[1], level_1_midpoints[2] }, level_3_midpoint, p2 });
|
||||||
|
};
|
||||||
|
|
||||||
|
Queue<SegmentDescriptor> segments;
|
||||||
|
segments.enqueue({ { control_point_0, control_point_1 }, p1, p2 });
|
||||||
|
while (!segments.is_empty()) {
|
||||||
|
auto segment = segments.dequeue();
|
||||||
|
|
||||||
|
if (can_approximate_cubic_bezier_curve(segment.p1, segment.p2, segment.control_points.control_point_0, segment.control_points.control_point_1))
|
||||||
|
callback(segment.p1, segment.p2);
|
||||||
|
else
|
||||||
|
split_cubic_bezier_curve(segment.control_points, segment.p1, segment.p2, segments);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Painter::draw_cubic_bezier_curve(IntPoint const& control_point_0, IntPoint const& control_point_1, IntPoint const& p1, IntPoint const& p2, Color color, int thickness, Painter::LineStyle style)
|
||||||
|
{
|
||||||
|
for_each_line_segment_on_cubic_bezier_curve(FloatPoint(control_point_0), FloatPoint(control_point_1), FloatPoint(p1), FloatPoint(p2), [&](FloatPoint const& fp1, FloatPoint const& fp2) {
|
||||||
|
draw_line(IntPoint(fp1.x(), fp1.y()), IntPoint(fp2.x(), fp2.y()), color, thickness, style);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// static
|
// static
|
||||||
void Painter::for_each_line_segment_on_elliptical_arc(FloatPoint const& p1, FloatPoint const& p2, FloatPoint const& center, FloatPoint const radii, float x_axis_rotation, float theta_1, float theta_delta, Function<void(FloatPoint const&, FloatPoint const&)>& callback)
|
void Painter::for_each_line_segment_on_elliptical_arc(FloatPoint const& p1, FloatPoint const& p2, FloatPoint const& center, FloatPoint const radii, float x_axis_rotation, float theta_1, float theta_delta, Function<void(FloatPoint const&, FloatPoint const&)>& callback)
|
||||||
{
|
{
|
||||||
|
@ -1975,6 +2045,14 @@ void Painter::stroke_path(Path const& path, Color color, int thickness)
|
||||||
cursor = segment.point();
|
cursor = segment.point();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case Segment::Type::CubicBezierCurveTo: {
|
||||||
|
auto& curve = static_cast<CubicBezierCurveSegment const&>(segment);
|
||||||
|
auto& through_0 = curve.through_0();
|
||||||
|
auto& through_1 = curve.through_1();
|
||||||
|
draw_cubic_bezier_curve(through_0.to_type<int>(), through_1.to_type<int>(), cursor.to_type<int>(), segment.point().to_type<int>(), color, thickness);
|
||||||
|
cursor = segment.point();
|
||||||
|
break;
|
||||||
|
}
|
||||||
case Segment::Type::EllipticalArcTo:
|
case Segment::Type::EllipticalArcTo:
|
||||||
auto& arc = static_cast<EllipticalArcSegment const&>(segment);
|
auto& arc = static_cast<EllipticalArcSegment const&>(segment);
|
||||||
draw_elliptical_arc(cursor.to_type<int>(), segment.point().to_type<int>(), arc.center().to_type<int>(), arc.radii(), arc.x_axis_rotation(), arc.theta_1(), arc.theta_delta(), color, thickness);
|
draw_elliptical_arc(cursor.to_type<int>(), segment.point().to_type<int>(), arc.center().to_type<int>(), arc.radii(), arc.x_axis_rotation(), arc.theta_1(), arc.theta_delta(), color, thickness);
|
||||||
|
|
|
@ -54,6 +54,7 @@ public:
|
||||||
void set_pixel(int x, int y, Color color) { set_pixel({ x, y }, color); }
|
void set_pixel(int x, int y, Color color) { set_pixel({ x, y }, color); }
|
||||||
void draw_line(IntPoint const&, IntPoint const&, Color, int thickness = 1, LineStyle style = LineStyle::Solid, Color alternate_color = Color::Transparent);
|
void draw_line(IntPoint const&, IntPoint const&, Color, int thickness = 1, LineStyle style = LineStyle::Solid, Color alternate_color = Color::Transparent);
|
||||||
void draw_quadratic_bezier_curve(IntPoint const& control_point, IntPoint const&, IntPoint const&, Color, int thickness = 1, LineStyle style = LineStyle::Solid);
|
void draw_quadratic_bezier_curve(IntPoint const& control_point, IntPoint const&, IntPoint const&, Color, int thickness = 1, LineStyle style = LineStyle::Solid);
|
||||||
|
void draw_cubic_bezier_curve(IntPoint const& control_point_0, IntPoint const& control_point_1, IntPoint const&, IntPoint const&, Color, int thickness = 1, LineStyle style = LineStyle::Solid);
|
||||||
void draw_elliptical_arc(IntPoint const& p1, IntPoint const& p2, IntPoint const& center, FloatPoint const& radii, float x_axis_rotation, float theta_1, float theta_delta, Color, int thickness = 1, LineStyle style = LineStyle::Solid);
|
void draw_elliptical_arc(IntPoint const& p1, IntPoint const& p2, IntPoint const& center, FloatPoint const& radii, float x_axis_rotation, float theta_1, float theta_delta, Color, int thickness = 1, LineStyle style = LineStyle::Solid);
|
||||||
void blit(IntPoint const&, Gfx::Bitmap const&, IntRect const& src_rect, float opacity = 1.0f, bool apply_alpha = true);
|
void blit(IntPoint const&, Gfx::Bitmap const&, IntRect const& src_rect, float opacity = 1.0f, bool apply_alpha = true);
|
||||||
void blit_dimmed(IntPoint const&, Gfx::Bitmap const&, IntRect const& src_rect);
|
void blit_dimmed(IntPoint const&, Gfx::Bitmap const&, IntRect const& src_rect);
|
||||||
|
@ -88,6 +89,9 @@ public:
|
||||||
static void for_each_line_segment_on_bezier_curve(FloatPoint const& control_point, FloatPoint const& p1, FloatPoint const& p2, Function<void(FloatPoint const&, FloatPoint const&)>&);
|
static void for_each_line_segment_on_bezier_curve(FloatPoint const& control_point, FloatPoint const& p1, FloatPoint const& p2, Function<void(FloatPoint const&, FloatPoint const&)>&);
|
||||||
static void for_each_line_segment_on_bezier_curve(FloatPoint const& control_point, FloatPoint const& p1, FloatPoint const& p2, Function<void(FloatPoint const&, FloatPoint const&)>&&);
|
static void for_each_line_segment_on_bezier_curve(FloatPoint const& control_point, FloatPoint const& p1, FloatPoint const& p2, Function<void(FloatPoint const&, FloatPoint const&)>&&);
|
||||||
|
|
||||||
|
static void for_each_line_segment_on_cubic_bezier_curve(FloatPoint const& control_point_0, FloatPoint const& control_point_1, FloatPoint const& p1, FloatPoint const& p2, Function<void(FloatPoint const&, FloatPoint const&)>&);
|
||||||
|
static void for_each_line_segment_on_cubic_bezier_curve(FloatPoint const& control_point_0, FloatPoint const& control_point_1, FloatPoint const& p1, FloatPoint const& p2, Function<void(FloatPoint const&, FloatPoint const&)>&&);
|
||||||
|
|
||||||
static void for_each_line_segment_on_elliptical_arc(FloatPoint const& p1, FloatPoint const& p2, FloatPoint const& center, FloatPoint const radii, float x_axis_rotation, float theta_1, float theta_delta, Function<void(FloatPoint const&, FloatPoint const&)>&);
|
static void for_each_line_segment_on_elliptical_arc(FloatPoint const& p1, FloatPoint const& p2, FloatPoint const& center, FloatPoint const radii, float x_axis_rotation, float theta_1, float theta_delta, Function<void(FloatPoint const&, FloatPoint const&)>&);
|
||||||
static void for_each_line_segment_on_elliptical_arc(FloatPoint const& p1, FloatPoint const& p2, FloatPoint const& center, FloatPoint const radii, float x_axis_rotation, float theta_1, float theta_delta, Function<void(FloatPoint const&, FloatPoint const&)>&&);
|
static void for_each_line_segment_on_elliptical_arc(FloatPoint const& p1, FloatPoint const& p2, FloatPoint const& center, FloatPoint const radii, float x_axis_rotation, float theta_1, float theta_delta, Function<void(FloatPoint const&, FloatPoint const&)>&&);
|
||||||
|
|
||||||
|
|
|
@ -160,6 +160,7 @@ void Path::close_all_subpaths()
|
||||||
}
|
}
|
||||||
case Segment::Type::LineTo:
|
case Segment::Type::LineTo:
|
||||||
case Segment::Type::QuadraticBezierCurveTo:
|
case Segment::Type::QuadraticBezierCurveTo:
|
||||||
|
case Segment::Type::CubicBezierCurveTo:
|
||||||
case Segment::Type::EllipticalArcTo:
|
case Segment::Type::EllipticalArcTo:
|
||||||
if (is_first_point_in_subpath) {
|
if (is_first_point_in_subpath) {
|
||||||
start_of_subpath = cursor;
|
start_of_subpath = cursor;
|
||||||
|
@ -189,6 +190,9 @@ String Path::to_string() const
|
||||||
case Segment::Type::QuadraticBezierCurveTo:
|
case Segment::Type::QuadraticBezierCurveTo:
|
||||||
builder.append("QuadraticBezierCurveTo");
|
builder.append("QuadraticBezierCurveTo");
|
||||||
break;
|
break;
|
||||||
|
case Segment::Type::CubicBezierCurveTo:
|
||||||
|
builder.append("CubicBezierCurveTo");
|
||||||
|
break;
|
||||||
case Segment::Type::EllipticalArcTo:
|
case Segment::Type::EllipticalArcTo:
|
||||||
builder.append("EllipticalArcTo");
|
builder.append("EllipticalArcTo");
|
||||||
break;
|
break;
|
||||||
|
@ -203,6 +207,12 @@ String Path::to_string() const
|
||||||
builder.append(", ");
|
builder.append(", ");
|
||||||
builder.append(static_cast<const QuadraticBezierCurveSegment&>(segment).through().to_string());
|
builder.append(static_cast<const QuadraticBezierCurveSegment&>(segment).through().to_string());
|
||||||
break;
|
break;
|
||||||
|
case Segment::Type::CubicBezierCurveTo:
|
||||||
|
builder.append(", ");
|
||||||
|
builder.append(static_cast<const CubicBezierCurveSegment&>(segment).through_0().to_string());
|
||||||
|
builder.append(", ");
|
||||||
|
builder.append(static_cast<const CubicBezierCurveSegment&>(segment).through_1().to_string());
|
||||||
|
break;
|
||||||
case Segment::Type::EllipticalArcTo: {
|
case Segment::Type::EllipticalArcTo: {
|
||||||
auto& arc = static_cast<const EllipticalArcSegment&>(segment);
|
auto& arc = static_cast<const EllipticalArcSegment&>(segment);
|
||||||
builder.appendff(", {}, {}, {}, {}, {}",
|
builder.appendff(", {}, {}, {}, {}, {}",
|
||||||
|
@ -286,6 +296,16 @@ void Path::segmentize_path()
|
||||||
cursor = segment.point();
|
cursor = segment.point();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case Segment::Type::CubicBezierCurveTo: {
|
||||||
|
auto& curve = static_cast<CubicBezierCurveSegment const&>(segment);
|
||||||
|
auto& control_0 = curve.through_0();
|
||||||
|
auto& control_1 = curve.through_1();
|
||||||
|
Painter::for_each_line_segment_on_cubic_bezier_curve(control_0, control_1, cursor, segment.point(), [&](const FloatPoint& p0, const FloatPoint& p1) {
|
||||||
|
add_line(p0, p1);
|
||||||
|
});
|
||||||
|
cursor = segment.point();
|
||||||
|
break;
|
||||||
|
}
|
||||||
case Segment::Type::EllipticalArcTo: {
|
case Segment::Type::EllipticalArcTo: {
|
||||||
auto& arc = static_cast<EllipticalArcSegment&>(segment);
|
auto& arc = static_cast<EllipticalArcSegment&>(segment);
|
||||||
Painter::for_each_line_segment_on_elliptical_arc(cursor, arc.point(), arc.center(), arc.radii(), arc.x_axis_rotation(), arc.theta_1(), arc.theta_delta(), [&](const FloatPoint& p0, const FloatPoint& p1) {
|
Painter::for_each_line_segment_on_elliptical_arc(cursor, arc.point(), arc.center(), arc.radii(), arc.x_axis_rotation(), arc.theta_1(), arc.theta_delta(), [&](const FloatPoint& p0, const FloatPoint& p1) {
|
||||||
|
@ -310,15 +330,4 @@ void Path::segmentize_path()
|
||||||
m_bounding_box = Gfx::FloatRect { min_x, min_y, max_x - min_x, max_y - min_y };
|
m_bounding_box = Gfx::FloatRect { min_x, min_y, max_x - min_x, max_y - min_y };
|
||||||
}
|
}
|
||||||
|
|
||||||
void Path::cubic_bezier_curve_to(FloatPoint const& c1, FloatPoint const& c2, FloatPoint const& p2)
|
|
||||||
{
|
|
||||||
// FIXME: I'm sure there's a faster and more elegant way to do this.
|
|
||||||
// FIXME: We should divide it into enough segments to stay within some tolerance.
|
|
||||||
auto p1 = segments().last().point();
|
|
||||||
for (float t = 0; t <= 1.0f; t += 0.02f) {
|
|
||||||
auto p = cubic_interpolate(p1, p2, c1, c2, t);
|
|
||||||
line_to(p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@ public:
|
||||||
MoveTo,
|
MoveTo,
|
||||||
LineTo,
|
LineTo,
|
||||||
QuadraticBezierCurveTo,
|
QuadraticBezierCurveTo,
|
||||||
|
CubicBezierCurveTo,
|
||||||
EllipticalArcTo,
|
EllipticalArcTo,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -83,6 +84,27 @@ private:
|
||||||
FloatPoint m_through;
|
FloatPoint m_through;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class CubicBezierCurveSegment final : public Segment {
|
||||||
|
public:
|
||||||
|
CubicBezierCurveSegment(const FloatPoint& point, const FloatPoint& through_0, const FloatPoint& through_1)
|
||||||
|
: Segment(point)
|
||||||
|
, m_through_0(through_0)
|
||||||
|
, m_through_1(through_1)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual ~CubicBezierCurveSegment() override = default;
|
||||||
|
|
||||||
|
const FloatPoint& through_0() const { return m_through_0; }
|
||||||
|
const FloatPoint& through_1() const { return m_through_1; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
virtual Type type() const override { return Segment::Type::CubicBezierCurveTo; }
|
||||||
|
|
||||||
|
FloatPoint m_through_0;
|
||||||
|
FloatPoint m_through_1;
|
||||||
|
};
|
||||||
|
|
||||||
class EllipticalArcSegment final : public Segment {
|
class EllipticalArcSegment final : public Segment {
|
||||||
public:
|
public:
|
||||||
EllipticalArcSegment(const FloatPoint& point, const FloatPoint& center, const FloatPoint radii, float x_axis_rotation, float theta_1, float theta_delta)
|
EllipticalArcSegment(const FloatPoint& point, const FloatPoint& center, const FloatPoint radii, float x_axis_rotation, float theta_1, float theta_delta)
|
||||||
|
@ -134,7 +156,11 @@ public:
|
||||||
invalidate_split_lines();
|
invalidate_split_lines();
|
||||||
}
|
}
|
||||||
|
|
||||||
void cubic_bezier_curve_to(FloatPoint const& c1, FloatPoint const& c2, FloatPoint const& p2);
|
void cubic_bezier_curve_to(FloatPoint const& c1, FloatPoint const& c2, FloatPoint const& p2)
|
||||||
|
{
|
||||||
|
append_segment<CubicBezierCurveSegment>(p2, c1, c2);
|
||||||
|
invalidate_split_lines();
|
||||||
|
}
|
||||||
|
|
||||||
void elliptical_arc_to(const FloatPoint& point, const FloatPoint& radii, double x_axis_rotation, bool large_arc, bool sweep);
|
void elliptical_arc_to(const FloatPoint& point, const FloatPoint& radii, double x_axis_rotation, bool large_arc, bool sweep);
|
||||||
void arc_to(const FloatPoint& point, float radius, bool large_arc, bool sweep)
|
void arc_to(const FloatPoint& point, float radius, bool large_arc, bool sweep)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue