diff options
Diffstat (limited to 'Userland')
-rw-r--r-- | Userland/Libraries/LibGfx/AntiAliasingPainter.cpp | 15 | ||||
-rw-r--r-- | Userland/Libraries/LibGfx/AntiAliasingPainter.h | 1 | ||||
-rw-r--r-- | Userland/Libraries/LibGfx/Painter.cpp | 78 | ||||
-rw-r--r-- | Userland/Libraries/LibGfx/Painter.h | 4 | ||||
-rw-r--r-- | Userland/Libraries/LibGfx/Path.cpp | 31 | ||||
-rw-r--r-- | Userland/Libraries/LibGfx/Path.h | 28 |
6 files changed, 145 insertions, 12 deletions
diff --git a/Userland/Libraries/LibGfx/AntiAliasingPainter.cpp b/Userland/Libraries/LibGfx/AntiAliasingPainter.cpp index 8d95a50c64..30dee5d413 100644 --- a/Userland/Libraries/LibGfx/AntiAliasingPainter.cpp +++ b/Userland/Libraries/LibGfx/AntiAliasingPainter.cpp @@ -132,6 +132,14 @@ void Gfx::AntiAliasingPainter::stroke_path(Path const& path, Color color, float cursor = segment.point(); 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: 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); @@ -154,3 +162,10 @@ void Gfx::AntiAliasingPainter::draw_quadratic_bezier_curve(FloatPoint const& con 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); + }); +} diff --git a/Userland/Libraries/LibGfx/AntiAliasingPainter.h b/Userland/Libraries/LibGfx/AntiAliasingPainter.h index 64a3a17a53..5ae54bd7d4 100644 --- a/Userland/Libraries/LibGfx/AntiAliasingPainter.h +++ b/Userland/Libraries/LibGfx/AntiAliasingPainter.h @@ -22,6 +22,7 @@ public: void fill_path(Path&, Color, Painter::WindingRule rule = Painter::WindingRule::Nonzero); 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_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 translate(float dx, float dy) { m_transform.translate(dx, dy); } diff --git a/Userland/Libraries/LibGfx/Painter.cpp b/Userland/Libraries/LibGfx/Painter.cpp index 22be719fdf..29bd782f39 100644 --- a/Userland/Libraries/LibGfx/Painter.cpp +++ b/Userland/Libraries/LibGfx/Painter.cpp @@ -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 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(); 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: 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); diff --git a/Userland/Libraries/LibGfx/Painter.h b/Userland/Libraries/LibGfx/Painter.h index d0a01d44cf..d87ef7aeec 100644 --- a/Userland/Libraries/LibGfx/Painter.h +++ b/Userland/Libraries/LibGfx/Painter.h @@ -54,6 +54,7 @@ public: 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_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 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); @@ -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_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&)>&&); diff --git a/Userland/Libraries/LibGfx/Path.cpp b/Userland/Libraries/LibGfx/Path.cpp index 248fc5dced..879e6e76c9 100644 --- a/Userland/Libraries/LibGfx/Path.cpp +++ b/Userland/Libraries/LibGfx/Path.cpp @@ -160,6 +160,7 @@ void Path::close_all_subpaths() } case Segment::Type::LineTo: case Segment::Type::QuadraticBezierCurveTo: + case Segment::Type::CubicBezierCurveTo: case Segment::Type::EllipticalArcTo: if (is_first_point_in_subpath) { start_of_subpath = cursor; @@ -189,6 +190,9 @@ String Path::to_string() const case Segment::Type::QuadraticBezierCurveTo: builder.append("QuadraticBezierCurveTo"); break; + case Segment::Type::CubicBezierCurveTo: + builder.append("CubicBezierCurveTo"); + break; case Segment::Type::EllipticalArcTo: builder.append("EllipticalArcTo"); break; @@ -203,6 +207,12 @@ String Path::to_string() const builder.append(", "); builder.append(static_cast<const QuadraticBezierCurveSegment&>(segment).through().to_string()); 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: { auto& arc = static_cast<const EllipticalArcSegment&>(segment); builder.appendff(", {}, {}, {}, {}, {}", @@ -286,6 +296,16 @@ void Path::segmentize_path() cursor = segment.point(); 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: { 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) { @@ -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 }; } -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); - } -} - } diff --git a/Userland/Libraries/LibGfx/Path.h b/Userland/Libraries/LibGfx/Path.h index afda4d00f7..41c0d65ade 100644 --- a/Userland/Libraries/LibGfx/Path.h +++ b/Userland/Libraries/LibGfx/Path.h @@ -24,6 +24,7 @@ public: MoveTo, LineTo, QuadraticBezierCurveTo, + CubicBezierCurveTo, EllipticalArcTo, }; @@ -83,6 +84,27 @@ private: 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 { public: 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(); } - 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 arc_to(const FloatPoint& point, float radius, bool large_arc, bool sweep) |