From ecc724ebfea2a5412aa209e7d386fe9d2e69ea21 Mon Sep 17 00:00:00 2001 From: Brandon DeRosier Date: Wed, 25 Jan 2023 02:25:08 -0800 Subject: [PATCH] [Impeller] Use analytical normals for stroke caps --- .../display_list/display_list_unittests.cc | 6 ++- impeller/entity/geometry.cc | 22 ++++++-- impeller/geometry/path.cc | 51 ++++++++++++++++++- impeller/geometry/path.h | 5 ++ impeller/geometry/path_component.cc | 26 ++++++++++ impeller/geometry/path_component.h | 25 +++++++-- 6 files changed, 124 insertions(+), 11 deletions(-) diff --git a/impeller/display_list/display_list_unittests.cc b/impeller/display_list/display_list_unittests.cc index df45f0c9f4256..b1c535badbd34 100644 --- a/impeller/display_list/display_list_unittests.cc +++ b/impeller/display_list/display_list_unittests.cc @@ -109,11 +109,13 @@ TEST_P(DisplayListTest, CanDrawArc) { auto callback = [&]() { static float start_angle = 45; static float sweep_angle = 270; + static float stroke_width = 10; static bool use_center = true; ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize); ImGui::SliderFloat("Start angle", &start_angle, -360, 360); ImGui::SliderFloat("Sweep angle", &sweep_angle, -360, 360); + ImGui::SliderFloat("Stroke width", &stroke_width, 0, 100); ImGui::Checkbox("Use center", &use_center); ImGui::End(); @@ -125,7 +127,7 @@ TEST_P(DisplayListTest, CanDrawArc) { Vector2 scale = GetContentScale(); builder.scale(scale.x, scale.y); builder.setStyle(flutter::DlDrawStyle::kStroke); - builder.setStrokeCap(flutter::DlStrokeCap::kRound); + builder.setStrokeCap(flutter::DlStrokeCap::kButt); builder.setStrokeJoin(flutter::DlStrokeJoin::kMiter); builder.setStrokeMiter(10); auto rect = SkRect::MakeLTRB(p1.x, p1.y, p2.x, p2.y); @@ -133,7 +135,7 @@ TEST_P(DisplayListTest, CanDrawArc) { builder.setStrokeWidth(2); builder.drawRect(rect); builder.setColor(SK_ColorRED); - builder.setStrokeWidth(10); + builder.setStrokeWidth(stroke_width); builder.drawArc(rect, start_angle, sweep_angle, use_center); return builder.Build(); diff --git a/impeller/entity/geometry.cc b/impeller/entity/geometry.cc index 177fc3dc90d20..6e1dd8a28df08 100644 --- a/impeller/entity/geometry.cc +++ b/impeller/entity/geometry.cc @@ -239,7 +239,13 @@ StrokePathGeometry::CapProc StrokePathGeometry::GetCapProc(Cap stroke_cap) { case Cap::kButt: cap_proc = [](VertexBufferBuilder& vtx_builder, const Point& position, const Point& offset, - Scalar tolerance) {}; + Scalar tolerance) { + VS::PerVertexData vtx; + vtx.position = position + offset; + vtx_builder.AppendVertex(vtx); + vtx.position = position - offset; + vtx_builder.AppendVertex(vtx); + }; break; case Cap::kRound: cap_proc = [](VertexBufferBuilder& vtx_builder, @@ -273,7 +279,6 @@ StrokePathGeometry::CapProc StrokePathGeometry::GetCapProc(Cap stroke_cap) { const Point& position, const Point& offset, Scalar tolerance) { VS::PerVertexData vtx; - vtx.position = position; Point forward(offset.y, -offset.x); @@ -319,6 +324,7 @@ VertexBuffer StrokePathGeometry::CreateSolidStrokeVertices( for (size_t contour_i = 0; contour_i < polyline.contours.size(); contour_i++) { + auto contour = polyline.contours[contour_i]; size_t contour_start_point_i, contour_end_point_i; std::tie(contour_start_point_i, contour_end_point_i) = polyline.GetContourPointBounds(contour_i); @@ -364,7 +370,10 @@ VertexBuffer StrokePathGeometry::CreateSolidStrokeVertices( // Generate start cap. if (!polyline.contours[contour_i].is_closed) { - cap_proc(vtx_builder, polyline.points[contour_start_point_i], -offset, + auto cap_offset = + Vector2(contour.start_direction.y, -contour.start_direction.x) * + stroke_width * 0.5; + cap_proc(vtx_builder, polyline.points[contour_start_point_i], cap_offset, tolerance); } @@ -394,8 +403,11 @@ VertexBuffer StrokePathGeometry::CreateSolidStrokeVertices( // Generate end cap or join. if (!polyline.contours[contour_i].is_closed) { - cap_proc(vtx_builder, polyline.points[contour_end_point_i - 1], offset, - tolerance); + auto cap_offset = + Vector2(-contour.end_direction.y, contour.end_direction.x) * + stroke_width * 0.5; + cap_proc(vtx_builder, polyline.points[contour_end_point_i - 1], + cap_offset, tolerance); } else { join_proc(vtx_builder, polyline.points[contour_start_point_i], offset, contour_first_offset, scaled_miter_limit, tolerance); diff --git a/impeller/geometry/path.cc b/impeller/geometry/path.cc index 68115f06533e5..cad3774c40863 100644 --- a/impeller/geometry/path.cc +++ b/impeller/geometry/path.cc @@ -242,18 +242,55 @@ Path::Polyline Path::CreatePolyline(Scalar tolerance) const { } }; + auto get_path_component = + [this](size_t component_i) -> std::optional { + if (component_i >= components_.size()) { + return std::nullopt; + } + const auto& component = components_[component_i]; + switch (component.type) { + case ComponentType::kLinear: + return &linears_[component.index]; + case ComponentType::kQuadratic: + return &quads_[component.index]; + case ComponentType::kCubic: + return &cubics_[component.index]; + case ComponentType::kContour: + return std::nullopt; + } + }; + + std::optional previous_path_component; + auto end_contour = [&polyline, &previous_path_component]() { + // Whenever a contour has ended, extract the exact end direction from the + // last component. + if (polyline.contours.empty()) { + return; + } + if (!previous_path_component.has_value()) { + return; + } + auto& contour = polyline.contours.back(); + contour.end_direction = + previous_path_component.value()->GetEndDirection().value_or( + Vector2(0, 1)); + }; + for (size_t component_i = 0; component_i < components_.size(); component_i++) { const auto& component = components_[component_i]; switch (component.type) { case ComponentType::kLinear: collect_points(linears_[component.index].CreatePolyline()); + previous_path_component = &linears_[component.index]; break; case ComponentType::kQuadratic: collect_points(quads_[component.index].CreatePolyline(tolerance)); + previous_path_component = &quads_[component.index]; break; case ComponentType::kCubic: collect_points(cubics_[component.index].CreatePolyline(tolerance)); + previous_path_component = &cubics_[component.index]; break; case ComponentType::kContour: if (component_i == components_.size() - 1) { @@ -261,13 +298,25 @@ Path::Polyline Path::CreatePolyline(Scalar tolerance) const { // contour, so skip it. continue; } + end_contour(); + + Vector2 start_direction(0, -1); + auto first_component = get_path_component(component_i + 1); + if (first_component.has_value()) { + start_direction = + first_component.value()->GetStartDirection().value_or( + Vector2(0, -1)); + } + const auto& contour = contours_[component.index]; polyline.contours.push_back({.start_index = polyline.points.size(), - .is_closed = contour.is_closed}); + .is_closed = contour.is_closed, + .start_direction = start_direction}); previous_contour_point = std::nullopt; collect_points({contour.destination}); break; } + end_contour(); } return polyline; } diff --git a/impeller/geometry/path.h b/impeller/geometry/path.h index 8ffce75882ecc..9e348cda2edcc 100644 --- a/impeller/geometry/path.h +++ b/impeller/geometry/path.h @@ -49,6 +49,11 @@ class Path { /// Denotes whether the last point of this contour is connected to the first /// point of this contour or not. bool is_closed; + + /// The direction of the contour's start cap. + Vector2 start_direction; + /// The direction of the contour's end cap. + Vector2 end_direction; }; /// One or more contours represented as a series of points and indices in diff --git a/impeller/geometry/path_component.cc b/impeller/geometry/path_component.cc index 04eb592cb6ac5..669fa4eaee0ff 100644 --- a/impeller/geometry/path_component.cc +++ b/impeller/geometry/path_component.cc @@ -8,6 +8,8 @@ namespace impeller { +PathComponent::~PathComponent() = default; + /* * Based on: https://en.wikipedia.org/wiki/B%C3%A9zier_curve#Specific_cases */ @@ -67,6 +69,14 @@ std::vector LinearPathComponent::Extrema() const { return {p1, p2}; } +std::optional LinearPathComponent::GetStartDirection() const { + return (p1 - p2).Normalize(); +} + +std::optional LinearPathComponent::GetEndDirection() const { + return (p2 - p1).Normalize(); +} + Point QuadraticPathComponent::Solve(Scalar time) const { return { QuadraticSolve(time, p1.x, cp.x, p2.x), // x @@ -139,6 +149,14 @@ std::vector QuadraticPathComponent::Extrema() const { return elevated.Extrema(); } +std::optional QuadraticPathComponent::GetStartDirection() const { + return (p1 - cp).Normalize(); +} + +std::optional QuadraticPathComponent::GetEndDirection() const { + return (p2 - cp).Normalize(); +} + Point CubicPathComponent::Solve(Scalar time) const { return { CubicSolve(time, p1.x, cp1.x, cp2.x, p2.x), // x @@ -283,4 +301,12 @@ std::vector CubicPathComponent::Extrema() const { return points; } +std::optional CubicPathComponent::GetStartDirection() const { + return (p1 - cp1).Normalize(); +} + +std::optional CubicPathComponent::GetEndDirection() const { + return (p2 - cp2).Normalize(); +} + } // namespace impeller diff --git a/impeller/geometry/path_component.h b/impeller/geometry/path_component.h index df658eaf580ce..2871192cc618a 100644 --- a/impeller/geometry/path_component.h +++ b/impeller/geometry/path_component.h @@ -22,7 +22,14 @@ namespace impeller { // points for the given scale. static constexpr Scalar kDefaultCurveTolerance = .1f; -struct LinearPathComponent { +struct PathComponent { + virtual ~PathComponent(); + + virtual std::optional GetStartDirection() const = 0; + virtual std::optional GetEndDirection() const = 0; +}; + +struct LinearPathComponent : public PathComponent { Point p1; Point p2; @@ -39,9 +46,13 @@ struct LinearPathComponent { bool operator==(const LinearPathComponent& other) const { return p1 == other.p1 && p2 == other.p2; } + + std::optional GetStartDirection() const override; + + std::optional GetEndDirection() const override; }; -struct QuadraticPathComponent { +struct QuadraticPathComponent : public PathComponent { Point p1; Point cp; Point p2; @@ -76,9 +87,13 @@ struct QuadraticPathComponent { bool operator==(const QuadraticPathComponent& other) const { return p1 == other.p1 && cp == other.cp && p2 == other.p2; } + + std::optional GetStartDirection() const override; + + std::optional GetEndDirection() const override; }; -struct CubicPathComponent { +struct CubicPathComponent : public PathComponent { Point p1; Point cp1; Point cp2; @@ -118,6 +133,10 @@ struct CubicPathComponent { p2 == other.p2; } + std::optional GetStartDirection() const override; + + std::optional GetEndDirection() const override; + private: QuadraticPathComponent Lower() const; };