From c36b54a7bfe2ddcc7addbdd4265fb1651a449319 Mon Sep 17 00:00:00 2001 From: MacDue Date: Mon, 5 Jun 2023 21:44:26 +0100 Subject: [PATCH] LibGfx: Use stroke_to_fill() for rendering strokes in the AA painter This probably should be cached somewhere, but this alone seems to give nice results, and performance does not seem much worse. Fixes #18519 --- .../Libraries/LibGfx/AntiAliasingPainter.cpp | 167 +----------------- .../Libraries/LibGfx/AntiAliasingPainter.h | 17 +- 2 files changed, 5 insertions(+), 179 deletions(-) diff --git a/Userland/Libraries/LibGfx/AntiAliasingPainter.cpp b/Userland/Libraries/LibGfx/AntiAliasingPainter.cpp index 684784f2ea..05ab9bf9a5 100644 --- a/Userland/Libraries/LibGfx/AntiAliasingPainter.cpp +++ b/Userland/Libraries/LibGfx/AntiAliasingPainter.cpp @@ -17,7 +17,6 @@ namespace Gfx { -template void AntiAliasingPainter::draw_anti_aliased_line(FloatPoint actual_from, FloatPoint actual_to, Color color, float thickness, Painter::LineStyle style, Color, LineLengthMode line_length_mode) { // FIXME: Implement this :P @@ -56,15 +55,6 @@ void AntiAliasingPainter::draw_anti_aliased_line(FloatPoint actual_from, FloatPo return fill_rect(Gfx::FloatRect(start_point, { thickness, length }), color); } - if constexpr (path_hacks == FixmeEnableHacksForBetterPathPainting::Yes) { - // FIXME: SVG stoke_path() hack: - // When painting stokes SVG asks for many very short lines... - // These look better just painted as dots/AA rectangles - // (Technically this should be rotated or a circle, but that currently gives worse results) - if (distance < 1.0f) - return fill_rect(Gfx::FloatRect::centered_at(mapped_from, { thickness, thickness }), color); - } - // The painting only works for the positive XY quadrant (because that is easier). // So flip things around until we're there: bool flip_x = false; @@ -148,11 +138,6 @@ void AntiAliasingPainter::draw_anti_aliased_line(FloatPoint actual_from, FloatPo } } -void AntiAliasingPainter::draw_line_for_path(FloatPoint actual_from, FloatPoint actual_to, Color color, float thickness, Painter::LineStyle style, Color alternate_color, LineLengthMode line_length_mode) -{ - draw_anti_aliased_line(actual_from, actual_to, color, thickness, style, alternate_color, line_length_mode); -} - void AntiAliasingPainter::draw_dotted_line(IntPoint point1, IntPoint point2, Color color, int thickness) { // AA circles don't really work below a radius of 2px. @@ -204,86 +189,13 @@ void AntiAliasingPainter::draw_line(FloatPoint actual_from, FloatPoint actual_to { if (style == Painter::LineStyle::Dotted) return draw_dotted_line(actual_from.to_rounded(), actual_to.to_rounded(), color, static_cast(round(thickness))); - draw_anti_aliased_line(actual_from, actual_to, color, thickness, style, alternate_color, line_length_mode); + draw_anti_aliased_line(actual_from, actual_to, color, thickness, style, alternate_color, line_length_mode); } void AntiAliasingPainter::stroke_path(Path const& path, Color color, float thickness) { - FloatPoint cursor; - bool previous_was_line = false; - FloatLine last_line; - Optional first_line; - - for (auto& segment : path.segments()) { - switch (segment->type()) { - case Segment::Type::Invalid: - VERIFY_NOT_REACHED(); - case Segment::Type::MoveTo: - cursor = segment->point(); - break; - case Segment::Type::LineTo: - draw_line(cursor, segment->point(), color, thickness); - if (thickness > 1) { - if (!first_line.has_value()) - first_line = FloatLine(cursor, segment->point()); - if (previous_was_line) - stroke_segment_intersection(cursor, segment->point(), last_line, color, thickness); - last_line.set_a(cursor); - last_line.set_b(segment->point()); - } - cursor = segment->point(); - break; - case Segment::Type::QuadraticBezierCurveTo: { - auto through = static_cast(*segment).through(); - draw_quadratic_bezier_curve(through, cursor, segment->point(), color, thickness); - cursor = segment->point(); - break; - } - case Segment::Type::CubicBezierCurveTo: { - auto& curve = static_cast(*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(*segment); - draw_elliptical_arc(cursor, segment->point(), arc.center(), arc.radii(), arc.x_axis_rotation(), arc.theta_1(), arc.theta_delta(), color, thickness); - cursor = segment->point(); - break; - } - - previous_was_line = segment->type() == Segment::Type::LineTo; - } - - // Check if the figure was started and closed as line at the same position. - if (thickness > 1 && previous_was_line && path.segments().size() >= 2 && path.segments().first()->point() == cursor - && (path.segments().first()->type() == Segment::Type::LineTo - || (path.segments().first()->type() == Segment::Type::MoveTo && path.segments()[1]->type() == Segment::Type::LineTo))) { - stroke_segment_intersection(first_line.value().a(), first_line.value().b(), last_line, color, thickness); - } -} - -void AntiAliasingPainter::draw_elliptical_arc(FloatPoint p1, FloatPoint p2, FloatPoint center, FloatSize radii, float x_axis_rotation, float theta_1, float theta_delta, Color color, float thickness, Painter::LineStyle style) -{ - Painter::for_each_line_segment_on_elliptical_arc(p1, p2, center, radii, x_axis_rotation, theta_1, theta_delta, [&](FloatPoint fp1, FloatPoint fp2) { - draw_line_for_path(fp1, fp2, color, thickness, style); - }); -} - -void AntiAliasingPainter::draw_quadratic_bezier_curve(FloatPoint control_point, FloatPoint p1, FloatPoint p2, Color color, float thickness, Painter::LineStyle style) -{ - Painter::for_each_line_segment_on_bezier_curve(control_point, p1, p2, [&](FloatPoint fp1, FloatPoint fp2) { - draw_line_for_path(fp1, fp2, color, thickness, style); - }); -} - -void AntiAliasingPainter::draw_cubic_bezier_curve(FloatPoint control_point_0, FloatPoint control_point_1, FloatPoint p1, FloatPoint p2, Color color, float thickness, Painter::LineStyle style) -{ - Painter::for_each_line_segment_on_cubic_bezier_curve(control_point_0, control_point_1, p1, p2, [&](FloatPoint fp1, FloatPoint fp2) { - draw_line_for_path(fp1, fp2, color, thickness, style); - }); + // FIXME: Cache this? Probably at a higher level such as in LibWeb? + fill_path(path.stroke_to_fill(thickness), color); } void AntiAliasingPainter::fill_rect(FloatRect const& float_rect, Color color) @@ -677,77 +589,4 @@ void AntiAliasingPainter::fill_rect_with_rounded_corners(IntRect const& a_rect, fill_corner(bottom_right_corner, bounding_rect.bottom_right().translated(-1), bottom_right); } -void AntiAliasingPainter::stroke_segment_intersection(FloatPoint current_line_a, FloatPoint current_line_b, FloatLine const& previous_line, Color color, float thickness) -{ - // FIXME: This is currently drawn in slightly the wrong place most of the time. - // FIXME: This is sometimes drawn when the intersection would not be visible anyway. - - // Starting point of the current line is where the last line ended... this is an intersection. - auto intersection = current_line_a; - - // If both are straight lines we can simply draw a rectangle at the intersection (or nothing). - auto current_vertical = current_line_a.x() == current_line_b.x(); - auto current_horizontal = current_line_a.y() == current_line_b.y(); - auto previous_vertical = previous_line.a().x() == previous_line.b().x(); - auto previous_horizontal = previous_line.a().y() == previous_line.b().y(); - if (previous_horizontal && current_horizontal) - return; - if (previous_vertical && current_vertical) - return; - if ((previous_horizontal || previous_vertical) && (current_horizontal || current_vertical)) { - intersection = m_transform.map(current_line_a); - // Note: int_thickness used here to match behavior of draw_line() - int int_thickness = AK::ceil(thickness); - return fill_rect(FloatRect(intersection, { thickness, thickness }).translated(-int_thickness / 2), color); - } - - auto previous_line_a = previous_line.a(); - float scale_to_move_current = (thickness / 2) / intersection.distance_from(current_line_b); - float scale_to_move_previous = (thickness / 2) / intersection.distance_from(previous_line_a); - - // Move the point on the line by half of the thickness. - float offset_current_edge_x = scale_to_move_current * (current_line_b.x() - intersection.x()); - float offset_current_edge_y = scale_to_move_current * (current_line_b.y() - intersection.y()); - float offset_prev_edge_x = scale_to_move_previous * (previous_line_a.x() - intersection.x()); - float offset_prev_edge_y = scale_to_move_previous * (previous_line_a.y() - intersection.y()); - - // Rotate the point by 90 and 270 degrees to get the points for both edges. - FloatPoint current_rotated_90deg(-offset_current_edge_y, offset_current_edge_x); - FloatPoint previous_rotated_90deg(-offset_prev_edge_y, offset_prev_edge_x); - auto current_rotated_270deg = intersection - current_rotated_90deg; - auto previous_rotated_270deg = intersection - previous_rotated_90deg; - - // Translate coordinates to the intersection point. - current_rotated_90deg += intersection; - previous_rotated_90deg += intersection; - - FloatLine outer_line_current_90(current_rotated_90deg, current_line_b - (intersection - current_rotated_90deg)); - FloatLine outer_line_current_270(current_rotated_270deg, current_line_b - (intersection - current_rotated_270deg)); - FloatLine outer_line_prev_270(previous_rotated_270deg, previous_line_a - (intersection - previous_rotated_270deg)); - FloatLine outer_line_prev_90(previous_rotated_90deg, previous_line_a - (intersection - previous_rotated_90deg)); - - auto edge_spike_90 = outer_line_current_90.intersected(outer_line_prev_270); - Optional edge_spike_270; - - if (edge_spike_90.has_value()) { - edge_spike_270 = intersection + (intersection - *edge_spike_90); - } else { - edge_spike_270 = outer_line_current_270.intersected(outer_line_prev_90); - if (edge_spike_270.has_value()) - edge_spike_90 = intersection + (intersection - *edge_spike_270); - } - - Path intersection_edge_path; - intersection_edge_path.move_to(current_rotated_90deg); - if (edge_spike_90.has_value()) - intersection_edge_path.line_to(*edge_spike_90); - intersection_edge_path.line_to(previous_rotated_270deg); - intersection_edge_path.line_to(current_rotated_270deg); - if (edge_spike_270.has_value()) - intersection_edge_path.line_to(*edge_spike_270); - intersection_edge_path.line_to(previous_rotated_90deg); - intersection_edge_path.close(); - fill_path(intersection_edge_path, color); -} - } diff --git a/Userland/Libraries/LibGfx/AntiAliasingPainter.h b/Userland/Libraries/LibGfx/AntiAliasingPainter.h index 23b791624b..ba6338e9c2 100644 --- a/Userland/Libraries/LibGfx/AntiAliasingPainter.h +++ b/Userland/Libraries/LibGfx/AntiAliasingPainter.h @@ -32,15 +32,11 @@ public: { draw_line(line.a(), line.b(), color, thickness, style, alternate_color, line_length_mode); } - void draw_line_for_path(FloatPoint, FloatPoint, Color, float thickness = 1, Painter::LineStyle style = Painter::LineStyle::Solid, Color alternate_color = Color::Transparent, LineLengthMode line_length_mode = LineLengthMode::PointToPoint); void fill_path(Path const&, Color, Painter::WindingRule rule = Painter::WindingRule::Nonzero); void fill_path(Path const&, PaintStyle const& paint_style, Painter::WindingRule rule = Painter::WindingRule::Nonzero); void stroke_path(Path const&, Color, float thickness); - void draw_quadratic_bezier_curve(FloatPoint control_point, FloatPoint, FloatPoint, Color, float thickness = 1, Painter::LineStyle style = Painter::LineStyle::Solid); - void draw_cubic_bezier_curve(FloatPoint control_point_0, FloatPoint control_point_1, FloatPoint, FloatPoint, Color, float thickness = 1, Painter::LineStyle style = Painter::LineStyle::Solid); - void draw_elliptical_arc(FloatPoint p1, FloatPoint p2, FloatPoint center, FloatSize 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(FloatPoint delta) { m_transform.translate(delta); } @@ -90,20 +86,11 @@ private: } }; - Range draw_ellipse_part(IntPoint a_rect, int radius_a, int radius_b, Color, bool flip_x_and_y, Optional x_clip, BlendMode blend_mode); + Range draw_ellipse_part(IntPoint a_rect, int radius_a, int radius_b, Color alternate_color, bool flip_x_and_y, Optional x_clip, BlendMode blend_mode); + void draw_anti_aliased_line(FloatPoint, FloatPoint, Color, float thickness, Painter::LineStyle, Color, LineLengthMode); void draw_dotted_line(IntPoint, IntPoint, Gfx::Color, int thickness); - enum class FixmeEnableHacksForBetterPathPainting { - Yes, - No, - }; - - template - void draw_anti_aliased_line(FloatPoint, FloatPoint, Color, float thickness, Painter::LineStyle style, Color alternate_color, LineLengthMode line_length_mode = LineLengthMode::PointToPoint); - - void stroke_segment_intersection(FloatPoint current_line_a, FloatPoint current_line_b, FloatLine const& previous_line, Color, float thickness); - Painter& m_underlying_painter; AffineTransform m_transform; };