diff --git a/ci/licenses_golden/excluded_files b/ci/licenses_golden/excluded_files index 04a4be99a0e27..e47182d24ba46 100644 --- a/ci/licenses_golden/excluded_files +++ b/ci/licenses_golden/excluded_files @@ -151,6 +151,7 @@ ../../../flutter/impeller/geometry/README.md ../../../flutter/impeller/geometry/geometry_unittests.cc ../../../flutter/impeller/geometry/matrix_unittests.cc +../../../flutter/impeller/geometry/path_unittests.cc ../../../flutter/impeller/geometry/rect_unittests.cc ../../../flutter/impeller/geometry/size_unittests.cc ../../../flutter/impeller/golden_tests/README.md diff --git a/impeller/display_list/skia_conversions.cc b/impeller/display_list/skia_conversions.cc index 56e2f87825ccd..a0b5eaf0ddd78 100644 --- a/impeller/display_list/skia_conversions.cc +++ b/impeller/display_list/skia_conversions.cc @@ -57,6 +57,8 @@ Path ToPath(const SkPath& path, Point shift) { PathBuilder builder; PathData data; + // Reserve a path size with some arbitrarily additional padding. + builder.Reserve(path.countPoints() + 8, path.countVerbs() + 8); auto verb = SkPath::Verb::kDone_Verb; do { verb = iterator.next(data.points); diff --git a/impeller/geometry/BUILD.gn b/impeller/geometry/BUILD.gn index 2ee32216b2c9b..72b8a01160153 100644 --- a/impeller/geometry/BUILD.gn +++ b/impeller/geometry/BUILD.gn @@ -62,6 +62,7 @@ impeller_component("geometry_unittests") { sources = [ "geometry_unittests.cc", "matrix_unittests.cc", + "path_unittests.cc", "rect_unittests.cc", "size_unittests.cc", ] diff --git a/impeller/geometry/geometry_unittests.cc b/impeller/geometry/geometry_unittests.cc index 4c5ea75435e95..0aaafd7ed3384 100644 --- a/impeller/geometry/geometry_unittests.cc +++ b/impeller/geometry/geometry_unittests.cc @@ -15,9 +15,6 @@ #include "impeller/geometry/constants.h" #include "impeller/geometry/gradient.h" #include "impeller/geometry/half.h" -#include "impeller/geometry/path.h" -#include "impeller/geometry/path_builder.h" -#include "impeller/geometry/path_component.h" #include "impeller/geometry/point.h" #include "impeller/geometry/rect.h" #include "impeller/geometry/scalar.h" @@ -591,121 +588,6 @@ TEST(GeometryTest, QuaternionVectorMultiply) { } } -TEST(GeometryTest, EmptyPath) { - auto path = PathBuilder{}.TakePath(); - ASSERT_EQ(path.GetComponentCount(), 1u); - - ContourComponent c; - path.GetContourComponentAtIndex(0, c); - ASSERT_POINT_NEAR(c.destination, Point()); - - Path::Polyline polyline = path.CreatePolyline(1.0f); - ASSERT_TRUE(polyline.points->empty()); - ASSERT_TRUE(polyline.contours.empty()); -} - -TEST(GeometryTest, SimplePath) { - PathBuilder builder; - - auto path = builder.AddLine({0, 0}, {100, 100}) - .AddQuadraticCurve({100, 100}, {200, 200}, {300, 300}) - .AddCubicCurve({300, 300}, {400, 400}, {500, 500}, {600, 600}) - .TakePath(); - - ASSERT_EQ(path.GetComponentCount(), 6u); - ASSERT_EQ(path.GetComponentCount(Path::ComponentType::kLinear), 1u); - ASSERT_EQ(path.GetComponentCount(Path::ComponentType::kQuadratic), 1u); - ASSERT_EQ(path.GetComponentCount(Path::ComponentType::kCubic), 1u); - ASSERT_EQ(path.GetComponentCount(Path::ComponentType::kContour), 3u); - - path.EnumerateComponents( - [](size_t index, const LinearPathComponent& linear) { - Point p1(0, 0); - Point p2(100, 100); - ASSERT_EQ(index, 1u); - ASSERT_EQ(linear.p1, p1); - ASSERT_EQ(linear.p2, p2); - }, - [](size_t index, const QuadraticPathComponent& quad) { - Point p1(100, 100); - Point cp(200, 200); - Point p2(300, 300); - ASSERT_EQ(index, 3u); - ASSERT_EQ(quad.p1, p1); - ASSERT_EQ(quad.cp, cp); - ASSERT_EQ(quad.p2, p2); - }, - [](size_t index, const CubicPathComponent& cubic) { - Point p1(300, 300); - Point cp1(400, 400); - Point cp2(500, 500); - Point p2(600, 600); - ASSERT_EQ(index, 5u); - ASSERT_EQ(cubic.p1, p1); - ASSERT_EQ(cubic.cp1, cp1); - ASSERT_EQ(cubic.cp2, cp2); - ASSERT_EQ(cubic.p2, p2); - }, - [](size_t index, const ContourComponent& contour) { - // There is an initial countour added for each curve. - if (index == 0u) { - Point p1(0, 0); - ASSERT_EQ(contour.destination, p1); - } else if (index == 2u) { - Point p1(100, 100); - ASSERT_EQ(contour.destination, p1); - } else if (index == 4u) { - Point p1(300, 300); - ASSERT_EQ(contour.destination, p1); - } else { - ASSERT_FALSE(true); - } - ASSERT_FALSE(contour.is_closed); - }); -} - -TEST(GeometryTest, BoundingBoxCubic) { - PathBuilder builder; - auto path = - builder.AddCubicCurve({120, 160}, {25, 200}, {220, 260}, {220, 40}) - .TakePath(); - auto box = path.GetBoundingBox(); - Rect expected = Rect::MakeXYWH(93.9101, 40, 126.09, 158.862); - ASSERT_TRUE(box.has_value()); - ASSERT_RECT_NEAR(box.value(), expected); -} - -TEST(GeometryTest, BoundingBoxOfCompositePathIsCorrect) { - PathBuilder builder; - builder.AddRoundedRect(Rect::MakeXYWH(10, 10, 300, 300), {50, 50, 50, 50}); - auto path = builder.TakePath(); - auto actual = path.GetBoundingBox(); - Rect expected = Rect::MakeXYWH(10, 10, 300, 300); - ASSERT_TRUE(actual.has_value()); - ASSERT_RECT_NEAR(actual.value(), expected); -} - -TEST(GeometryTest, ExtremaOfCubicPathComponentIsCorrect) { - CubicPathComponent cubic{{11.769268, 252.883148}, - {-6.2857933, 204.356461}, - {-4.53997231, 156.552902}, - {17.0067291, 109.472488}}; - auto points = cubic.Extrema(); - ASSERT_EQ(points.size(), static_cast(3)); - ASSERT_POINT_NEAR(points[2], cubic.Solve(0.455916)); -} - -TEST(GeometryTest, PathGetBoundingBoxForCubicWithNoDerivativeRootsIsCorrect) { - PathBuilder builder; - // Straight diagonal line. - builder.AddCubicCurve({0, 1}, {2, 3}, {4, 5}, {6, 7}); - auto path = builder.TakePath(); - auto actual = path.GetBoundingBox(); - auto expected = Rect::MakeLTRB(0, 1, 6, 7); - ASSERT_TRUE(actual.has_value()); - ASSERT_RECT_NEAR(actual.value(), expected); -} - TEST(GeometryTest, CanGenerateMipCounts) { ASSERT_EQ((Size{128, 128}.MipCount()), 7u); ASSERT_EQ((Size{128, 256}.MipCount()), 8u); @@ -2097,212 +1979,6 @@ TEST(GeometryTest, RectRoundOut) { } } -TEST(GeometryTest, CubicPathComponentPolylineDoesNotIncludePointOne) { - CubicPathComponent component({10, 10}, {20, 35}, {35, 20}, {40, 40}); - std::vector polyline; - component.AppendPolylinePoints(1.0f, polyline); - ASSERT_NE(polyline.front().x, 10); - ASSERT_NE(polyline.front().y, 10); - ASSERT_EQ(polyline.back().x, 40); - ASSERT_EQ(polyline.back().y, 40); -} - -TEST(GeometryTest, PathCreatePolyLineDoesNotDuplicatePoints) { - PathBuilder builder; - builder.MoveTo({10, 10}); - builder.LineTo({20, 20}); - builder.LineTo({30, 30}); - builder.MoveTo({40, 40}); - builder.LineTo({50, 50}); - - auto polyline = builder.TakePath().CreatePolyline(1.0f); - - ASSERT_EQ(polyline.contours.size(), 2u); - ASSERT_EQ(polyline.points->size(), 5u); - ASSERT_EQ(polyline.GetPoint(0).x, 10); - ASSERT_EQ(polyline.GetPoint(1).x, 20); - ASSERT_EQ(polyline.GetPoint(2).x, 30); - ASSERT_EQ(polyline.GetPoint(3).x, 40); - ASSERT_EQ(polyline.GetPoint(4).x, 50); -} - -TEST(GeometryTest, PathBuilderSetsCorrectContourPropertiesForAddCommands) { - // Closed shapes. - { - Path path = PathBuilder{}.AddCircle({100, 100}, 50).TakePath(); - ContourComponent contour; - path.GetContourComponentAtIndex(0, contour); - ASSERT_POINT_NEAR(contour.destination, Point(100, 50)); - ASSERT_TRUE(contour.is_closed); - } - - { - Path path = - PathBuilder{}.AddOval(Rect::MakeXYWH(100, 100, 100, 100)).TakePath(); - ContourComponent contour; - path.GetContourComponentAtIndex(0, contour); - ASSERT_POINT_NEAR(contour.destination, Point(150, 100)); - ASSERT_TRUE(contour.is_closed); - } - - { - Path path = - PathBuilder{}.AddRect(Rect::MakeXYWH(100, 100, 100, 100)).TakePath(); - ContourComponent contour; - path.GetContourComponentAtIndex(0, contour); - ASSERT_POINT_NEAR(contour.destination, Point(100, 100)); - ASSERT_TRUE(contour.is_closed); - } - - { - Path path = PathBuilder{} - .AddRoundedRect(Rect::MakeXYWH(100, 100, 100, 100), 10) - .TakePath(); - ContourComponent contour; - path.GetContourComponentAtIndex(0, contour); - ASSERT_POINT_NEAR(contour.destination, Point(110, 100)); - ASSERT_TRUE(contour.is_closed); - } - - // Open shapes. - { - Point p(100, 100); - Path path = PathBuilder{}.AddLine(p, {200, 100}).TakePath(); - ContourComponent contour; - path.GetContourComponentAtIndex(0, contour); - ASSERT_POINT_NEAR(contour.destination, p); - ASSERT_FALSE(contour.is_closed); - } - - { - Path path = - PathBuilder{} - .AddCubicCurve({100, 100}, {100, 50}, {100, 150}, {200, 100}) - .TakePath(); - ContourComponent contour; - path.GetContourComponentAtIndex(0, contour); - ASSERT_POINT_NEAR(contour.destination, Point(100, 100)); - ASSERT_FALSE(contour.is_closed); - } - - { - Path path = PathBuilder{} - .AddQuadraticCurve({100, 100}, {100, 50}, {200, 100}) - .TakePath(); - ContourComponent contour; - path.GetContourComponentAtIndex(0, contour); - ASSERT_POINT_NEAR(contour.destination, Point(100, 100)); - ASSERT_FALSE(contour.is_closed); - } -} - -TEST(GeometryTest, PathCreatePolylineGeneratesCorrectContourData) { - Path::Polyline polyline = PathBuilder{} - .AddLine({100, 100}, {200, 100}) - .MoveTo({100, 200}) - .LineTo({150, 250}) - .LineTo({200, 200}) - .Close() - .TakePath() - .CreatePolyline(1.0f); - ASSERT_EQ(polyline.points->size(), 6u); - ASSERT_EQ(polyline.contours.size(), 2u); - ASSERT_EQ(polyline.contours[0].is_closed, false); - ASSERT_EQ(polyline.contours[0].start_index, 0u); - ASSERT_EQ(polyline.contours[1].is_closed, true); - ASSERT_EQ(polyline.contours[1].start_index, 2u); -} - -TEST(GeometryTest, PolylineGetContourPointBoundsReturnsCorrectRanges) { - Path::Polyline polyline = PathBuilder{} - .AddLine({100, 100}, {200, 100}) - .MoveTo({100, 200}) - .LineTo({150, 250}) - .LineTo({200, 200}) - .Close() - .TakePath() - .CreatePolyline(1.0f); - size_t a1, a2, b1, b2; - std::tie(a1, a2) = polyline.GetContourPointBounds(0); - std::tie(b1, b2) = polyline.GetContourPointBounds(1); - ASSERT_EQ(a1, 0u); - ASSERT_EQ(a2, 2u); - ASSERT_EQ(b1, 2u); - ASSERT_EQ(b2, 6u); -} - -TEST(GeometryTest, PathAddRectPolylineHasCorrectContourData) { - Path::Polyline polyline = PathBuilder{} - .AddRect(Rect::MakeLTRB(50, 60, 70, 80)) - .TakePath() - .CreatePolyline(1.0f); - ASSERT_EQ(polyline.contours.size(), 1u); - ASSERT_TRUE(polyline.contours[0].is_closed); - ASSERT_EQ(polyline.contours[0].start_index, 0u); - ASSERT_EQ(polyline.points->size(), 5u); - ASSERT_EQ(polyline.GetPoint(0), Point(50, 60)); - ASSERT_EQ(polyline.GetPoint(1), Point(70, 60)); - ASSERT_EQ(polyline.GetPoint(2), Point(70, 80)); - ASSERT_EQ(polyline.GetPoint(3), Point(50, 80)); - ASSERT_EQ(polyline.GetPoint(4), Point(50, 60)); -} - -TEST(GeometryTest, PathPolylineDuplicatesAreRemovedForSameContour) { - Path::Polyline polyline = - PathBuilder{} - .MoveTo({50, 50}) - .LineTo({50, 50}) // Insert duplicate at beginning of contour. - .LineTo({100, 50}) - .LineTo({100, 50}) // Insert duplicate at contour join. - .LineTo({100, 100}) - .Close() // Implicitly insert duplicate {50, 50} across contours. - .LineTo({0, 50}) - .LineTo({0, 100}) - .LineTo({0, 100}) // Insert duplicate at end of contour. - .TakePath() - .CreatePolyline(1.0f); - ASSERT_EQ(polyline.contours.size(), 2u); - ASSERT_EQ(polyline.contours[0].start_index, 0u); - ASSERT_TRUE(polyline.contours[0].is_closed); - ASSERT_EQ(polyline.contours[1].start_index, 4u); - ASSERT_FALSE(polyline.contours[1].is_closed); - ASSERT_EQ(polyline.points->size(), 7u); - ASSERT_EQ(polyline.GetPoint(0), Point(50, 50)); - ASSERT_EQ(polyline.GetPoint(1), Point(100, 50)); - ASSERT_EQ(polyline.GetPoint(2), Point(100, 100)); - ASSERT_EQ(polyline.GetPoint(3), Point(50, 50)); - ASSERT_EQ(polyline.GetPoint(4), Point(50, 50)); - ASSERT_EQ(polyline.GetPoint(5), Point(0, 50)); - ASSERT_EQ(polyline.GetPoint(6), Point(0, 100)); -} - -TEST(GeometryTest, PolylineBufferReuse) { - auto point_buffer = std::make_unique>(); - auto point_buffer_address = reinterpret_cast(point_buffer.get()); - Path::Polyline polyline = - PathBuilder{} - .MoveTo({50, 50}) - .LineTo({100, 100}) - .TakePath() - .CreatePolyline( - 1.0f, std::move(point_buffer), - [point_buffer_address]( - Path::Polyline::PointBufferPtr point_buffer) { - ASSERT_EQ(point_buffer->size(), 0u); - ASSERT_EQ(point_buffer_address, - reinterpret_cast(point_buffer.get())); - }); -} - -TEST(GeometryTest, PolylineFailsWithNullptrBuffer) { - EXPECT_DEATH_IF_SUPPORTED(PathBuilder{} - .MoveTo({50, 50}) - .LineTo({100, 100}) - .TakePath() - .CreatePolyline(1.0f, nullptr), - ""); -} - TEST(GeometryTest, MatrixPrinting) { { std::stringstream stream; @@ -2504,61 +2180,6 @@ TEST(GeometryTest, HalfConversions) { #endif // FML_OS_WIN } -TEST(GeometryTest, PathShifting) { - PathBuilder builder{}; - auto path = - builder.AddLine(Point(0, 0), Point(10, 10)) - .AddQuadraticCurve(Point(10, 10), Point(15, 15), Point(20, 20)) - .AddCubicCurve(Point(20, 20), Point(25, 25), Point(-5, -5), - Point(30, 30)) - .Close() - .Shift(Point(1, 1)) - .TakePath(); - - ContourComponent contour; - LinearPathComponent linear; - QuadraticPathComponent quad; - CubicPathComponent cubic; - - ASSERT_TRUE(path.GetContourComponentAtIndex(0, contour)); - ASSERT_TRUE(path.GetLinearComponentAtIndex(1, linear)); - ASSERT_TRUE(path.GetQuadraticComponentAtIndex(3, quad)); - ASSERT_TRUE(path.GetCubicComponentAtIndex(5, cubic)); - - ASSERT_EQ(contour.destination, Point(1, 1)); - - ASSERT_EQ(linear.p1, Point(1, 1)); - ASSERT_EQ(linear.p2, Point(11, 11)); - - ASSERT_EQ(quad.cp, Point(16, 16)); - ASSERT_EQ(quad.p1, Point(11, 11)); - ASSERT_EQ(quad.p2, Point(21, 21)); - - ASSERT_EQ(cubic.cp1, Point(26, 26)); - ASSERT_EQ(cubic.cp2, Point(-4, -4)); - ASSERT_EQ(cubic.p1, Point(21, 21)); - ASSERT_EQ(cubic.p2, Point(31, 31)); -} - -TEST(GeometryTest, PathBuilderWillComputeBounds) { - PathBuilder builder; - auto path_1 = builder.AddLine({0, 0}, {1, 1}).TakePath(); - - ASSERT_EQ(path_1.GetBoundingBox().value(), Rect::MakeLTRB(0, 0, 1, 1)); - - auto path_2 = builder.AddLine({-1, -1}, {1, 1}).TakePath(); - - // Verify that PathBuilder recomputes the bounds. - ASSERT_EQ(path_2.GetBoundingBox().value(), Rect::MakeLTRB(-1, -1, 1, 1)); - - // PathBuilder can set the bounds to whatever it wants - auto path_3 = builder.AddLine({0, 0}, {1, 1}) - .SetBounds(Rect::MakeLTRB(0, 0, 100, 100)) - .TakePath(); - - ASSERT_EQ(path_3.GetBoundingBox().value(), Rect::MakeLTRB(0, 0, 100, 100)); -} - } // namespace testing } // namespace impeller diff --git a/impeller/geometry/path.cc b/impeller/geometry/path.cc index 5f99a5cfeb6f9..3884dc8e21af7 100644 --- a/impeller/geometry/path.cc +++ b/impeller/geometry/path.cc @@ -32,19 +32,20 @@ std::tuple Path::Polyline::GetContourPointBounds( } size_t Path::GetComponentCount(std::optional type) const { - if (type.has_value()) { - switch (type.value()) { - case ComponentType::kLinear: - return linears_.size(); - case ComponentType::kQuadratic: - return quads_.size(); - case ComponentType::kCubic: - return cubics_.size(); - case ComponentType::kContour: - return contours_.size(); + if (!type.has_value()) { + return components_.size(); + } + auto type_value = type.value(); + if (type_value == ComponentType::kContour) { + return contours_.size(); + } + size_t count = 0u; + for (const auto& component : components_) { + if (component.type == type_value) { + count++; } } - return components_.size(); + return count; } void Path::SetFillType(FillType fill) { @@ -64,51 +65,47 @@ void Path::SetConvexity(Convexity value) { } void Path::Shift(Point shift) { - size_t currentIndex = 0; - for (const auto& component : components_) { - switch (component.type) { - case ComponentType::kLinear: - linears_[component.index].p1 += shift; - linears_[component.index].p2 += shift; - break; - case ComponentType::kQuadratic: - quads_[component.index].cp += shift; - quads_[component.index].p1 += shift; - quads_[component.index].p2 += shift; - break; - case ComponentType::kCubic: - cubics_[component.index].cp1 += shift; - cubics_[component.index].cp2 += shift; - cubics_[component.index].p1 += shift; - cubics_[component.index].p2 += shift; - break; - case ComponentType::kContour: - contours_[component.index].destination += shift; - break; - } - currentIndex++; + for (auto i = 0u; i < points_.size(); i++) { + points_[i] += shift; + } + for (auto& contour : contours_) { + contour.destination += shift; } } -Path& Path::AddLinearComponent(Point p1, Point p2) { - linears_.emplace_back(p1, p2); - components_.emplace_back(ComponentType::kLinear, linears_.size() - 1); +Path& Path::AddLinearComponent(const Point& p1, const Point& p2) { + auto index = points_.size(); + points_.emplace_back(p1); + points_.emplace_back(p2); + components_.emplace_back(ComponentType::kLinear, index); return *this; } -Path& Path::AddQuadraticComponent(Point p1, Point cp, Point p2) { - quads_.emplace_back(p1, cp, p2); - components_.emplace_back(ComponentType::kQuadratic, quads_.size() - 1); +Path& Path::AddQuadraticComponent(const Point& p1, + const Point& cp, + const Point& p2) { + auto index = points_.size(); + points_.emplace_back(p1); + points_.emplace_back(cp); + points_.emplace_back(p2); + components_.emplace_back(ComponentType::kQuadratic, index); return *this; } -Path& Path::AddCubicComponent(Point p1, Point cp1, Point cp2, Point p2) { - cubics_.emplace_back(p1, cp1, cp2, p2); - components_.emplace_back(ComponentType::kCubic, cubics_.size() - 1); +Path& Path::AddCubicComponent(const Point& p1, + const Point& cp1, + const Point& cp2, + const Point& p2) { + auto index = points_.size(); + points_.emplace_back(p1); + points_.emplace_back(cp1); + points_.emplace_back(cp2); + points_.emplace_back(p2); + components_.emplace_back(ComponentType::kCubic, index); return *this; } -Path& Path::AddContourComponent(Point destination, bool is_closed) { +Path& Path::AddContourComponent(const Point& destination, bool is_closed) { if (components_.size() > 0 && components_.back().type == ComponentType::kContour) { // Never insert contiguous contours. @@ -134,17 +131,26 @@ void Path::EnumerateComponents( switch (component.type) { case ComponentType::kLinear: if (linear_applier) { - linear_applier(currentIndex, linears_[component.index]); + linear_applier(currentIndex, + LinearPathComponent(points_[component.index], + points_[component.index + 1])); } break; case ComponentType::kQuadratic: if (quad_applier) { - quad_applier(currentIndex, quads_[component.index]); + quad_applier(currentIndex, + QuadraticPathComponent(points_[component.index], + points_[component.index + 1], + points_[component.index + 2])); } break; case ComponentType::kCubic: if (cubic_applier) { - cubic_applier(currentIndex, cubics_[component.index]); + cubic_applier(currentIndex, + CubicPathComponent(points_[component.index], + points_[component.index + 1], + points_[component.index + 2], + points_[component.index + 3])); } break; case ComponentType::kContour: @@ -167,7 +173,8 @@ bool Path::GetLinearComponentAtIndex(size_t index, return false; } - linear = linears_[components_[index].index]; + auto point_index = components_[index].index; + linear = LinearPathComponent(points_[point_index], points_[point_index + 1]); return true; } @@ -182,7 +189,9 @@ bool Path::GetQuadraticComponentAtIndex( return false; } - quadratic = quads_[components_[index].index]; + auto point_index = components_[index].index; + quadratic = QuadraticPathComponent( + points_[point_index], points_[point_index + 1], points_[point_index + 2]); return true; } @@ -196,7 +205,10 @@ bool Path::GetCubicComponentAtIndex(size_t index, return false; } - cubic = cubics_[components_[index].index]; + auto point_index = components_[index].index; + cubic = + CubicPathComponent(points_[point_index], points_[point_index + 1], + points_[point_index + 2], points_[point_index + 3]); return true; } @@ -246,11 +258,14 @@ Path::Polyline Path::CreatePolyline( const auto& component = components_[component_i]; switch (component.type) { case ComponentType::kLinear: - return &linears_[component.index]; + return reinterpret_cast( + &points_[component.index]); case ComponentType::kQuadratic: - return &quads_[component.index]; + return reinterpret_cast( + &points_[component.index]); case ComponentType::kCubic: - return &cubics_[component.index]; + return reinterpret_cast( + &points_[component.index]); case ComponentType::kContour: return std::monostate{}; } @@ -319,7 +334,8 @@ Path::Polyline Path::CreatePolyline( .component_start_index = polyline.points->size() - 1, .is_curve = false, }); - linears_[component.index].AppendPolylinePoints(*polyline.points); + reinterpret_cast(&points_[component.index]) + ->AppendPolylinePoints(*polyline.points); previous_path_component_index = component_i; break; case ComponentType::kQuadratic: @@ -327,7 +343,9 @@ Path::Polyline Path::CreatePolyline( .component_start_index = polyline.points->size() - 1, .is_curve = true, }); - quads_[component.index].AppendPolylinePoints(scale, *polyline.points); + reinterpret_cast( + &points_[component.index]) + ->AppendPolylinePoints(scale, *polyline.points); previous_path_component_index = component_i; break; case ComponentType::kCubic: @@ -335,7 +353,8 @@ Path::Polyline Path::CreatePolyline( .component_start_index = polyline.points->size() - 1, .is_curve = true, }); - cubics_[component.index].AppendPolylinePoints(scale, *polyline.points); + reinterpret_cast(&points_[component.index]) + ->AppendPolylinePoints(scale, *polyline.points); previous_path_component_index = component_i; break; case ComponentType::kContour: @@ -387,7 +406,7 @@ std::optional Path::GetTransformedBoundingBox( } std::optional> Path::GetMinMaxCoveragePoints() const { - if (linears_.empty() && quads_.empty() && cubics_.empty()) { + if (points_.empty()) { return std::nullopt; } @@ -407,20 +426,32 @@ std::optional> Path::GetMinMaxCoveragePoints() const { } }; - for (const auto& linear : linears_) { - clamp(linear.p1); - clamp(linear.p2); - } - - for (const auto& quad : quads_) { - for (const Point& point : quad.Extrema()) { - clamp(point); - } - } - - for (const auto& cubic : cubics_) { - for (const Point& point : cubic.Extrema()) { - clamp(point); + for (const auto& component : components_) { + switch (component.type) { + case ComponentType::kLinear: { + auto* linear = reinterpret_cast( + &points_[component.index]); + clamp(linear->p1); + clamp(linear->p2); + break; + } + case ComponentType::kQuadratic: + for (const auto& extrema : + reinterpret_cast( + &points_[component.index]) + ->Extrema()) { + clamp(extrema); + } + break; + case ComponentType::kCubic: + for (const auto& extrema : reinterpret_cast( + &points_[component.index]) + ->Extrema()) { + clamp(extrema); + } + break; + case ComponentType::kContour: + break; } } diff --git a/impeller/geometry/path.h b/impeller/geometry/path.h index 0733ad833cac9..26f0069604d18 100644 --- a/impeller/geometry/path.h +++ b/impeller/geometry/path.h @@ -184,13 +184,18 @@ class Path { void SetBounds(Rect rect); - Path& AddLinearComponent(Point p1, Point p2); + Path& AddLinearComponent(const Point& p1, const Point& p2); - Path& AddQuadraticComponent(Point p1, Point cp, Point p2); + Path& AddQuadraticComponent(const Point& p1, + const Point& cp, + const Point& p2); - Path& AddCubicComponent(Point p1, Point cp1, Point cp2, Point p2); + Path& AddCubicComponent(const Point& p1, + const Point& cp1, + const Point& cp2, + const Point& p2); - Path& AddContourComponent(Point destination, bool is_closed = false); + Path& AddContourComponent(const Point& destination, bool is_closed = false); /// @brief Called by `PathBuilder` to compute the bounds for certain paths. /// @@ -215,9 +220,7 @@ class Path { FillType fill_ = FillType::kNonZero; Convexity convexity_ = Convexity::kUnknown; std::vector components_; - std::vector linears_; - std::vector quads_; - std::vector cubics_; + std::vector points_; std::vector contours_; std::optional computed_bounds_; diff --git a/impeller/geometry/path_builder.cc b/impeller/geometry/path_builder.cc index 85544755f8cf1..2271133cbf909 100644 --- a/impeller/geometry/path_builder.cc +++ b/impeller/geometry/path_builder.cc @@ -29,6 +29,11 @@ Path PathBuilder::TakePath(FillType fill) { return path; } +void PathBuilder::Reserve(size_t point_size, size_t verb_size) { + prototype_.points_.reserve(point_size); + prototype_.points_.reserve(verb_size); +} + PathBuilder& PathBuilder::MoveTo(Point point, bool relative) { current_ = relative ? current_ + point : point; subpath_start_ = current_; diff --git a/impeller/geometry/path_builder.h b/impeller/geometry/path_builder.h index e15b7e3abf7ed..1502944f00bfd 100644 --- a/impeller/geometry/path_builder.h +++ b/impeller/geometry/path_builder.h @@ -29,6 +29,10 @@ class PathBuilder { Path TakePath(FillType fill = FillType::kNonZero); + /// @brief Reserve [point_size] points and [verb_size] verbs in the underlying + /// path buffer. + void Reserve(size_t point_size, size_t verb_size); + const Path& GetCurrentPath() const; PathBuilder& SetConvexity(Convexity value); diff --git a/impeller/geometry/path_unittests.cc b/impeller/geometry/path_unittests.cc new file mode 100644 index 0000000000000..e4f5df5b65a6e --- /dev/null +++ b/impeller/geometry/path_unittests.cc @@ -0,0 +1,447 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "gtest/gtest.h" + +#include "flutter/testing/testing.h" +#include "impeller/geometry/geometry_asserts.h" +#include "impeller/geometry/path.h" +#include "impeller/geometry/path_builder.h" + +namespace impeller { +namespace testing { + +TEST(PathTest, CubicPathComponentPolylineDoesNotIncludePointOne) { + CubicPathComponent component({10, 10}, {20, 35}, {35, 20}, {40, 40}); + std::vector polyline; + component.AppendPolylinePoints(1.0f, polyline); + ASSERT_NE(polyline.front().x, 10); + ASSERT_NE(polyline.front().y, 10); + ASSERT_EQ(polyline.back().x, 40); + ASSERT_EQ(polyline.back().y, 40); +} + +TEST(PathTest, PathCreatePolyLineDoesNotDuplicatePoints) { + PathBuilder builder; + builder.MoveTo({10, 10}); + builder.LineTo({20, 20}); + builder.LineTo({30, 30}); + builder.MoveTo({40, 40}); + builder.LineTo({50, 50}); + + auto polyline = builder.TakePath().CreatePolyline(1.0f); + + ASSERT_EQ(polyline.contours.size(), 2u); + ASSERT_EQ(polyline.points->size(), 5u); + ASSERT_EQ(polyline.GetPoint(0).x, 10); + ASSERT_EQ(polyline.GetPoint(1).x, 20); + ASSERT_EQ(polyline.GetPoint(2).x, 30); + ASSERT_EQ(polyline.GetPoint(3).x, 40); + ASSERT_EQ(polyline.GetPoint(4).x, 50); +} + +TEST(PathTest, PathBuilderSetsCorrectContourPropertiesForAddCommands) { + // Closed shapes. + { + Path path = PathBuilder{}.AddCircle({100, 100}, 50).TakePath(); + ContourComponent contour; + path.GetContourComponentAtIndex(0, contour); + ASSERT_POINT_NEAR(contour.destination, Point(100, 50)); + ASSERT_TRUE(contour.is_closed); + } + + { + Path path = + PathBuilder{}.AddOval(Rect::MakeXYWH(100, 100, 100, 100)).TakePath(); + ContourComponent contour; + path.GetContourComponentAtIndex(0, contour); + ASSERT_POINT_NEAR(contour.destination, Point(150, 100)); + ASSERT_TRUE(contour.is_closed); + } + + { + Path path = + PathBuilder{}.AddRect(Rect::MakeXYWH(100, 100, 100, 100)).TakePath(); + ContourComponent contour; + path.GetContourComponentAtIndex(0, contour); + ASSERT_POINT_NEAR(contour.destination, Point(100, 100)); + ASSERT_TRUE(contour.is_closed); + } + + { + Path path = PathBuilder{} + .AddRoundedRect(Rect::MakeXYWH(100, 100, 100, 100), 10) + .TakePath(); + ContourComponent contour; + path.GetContourComponentAtIndex(0, contour); + ASSERT_POINT_NEAR(contour.destination, Point(110, 100)); + ASSERT_TRUE(contour.is_closed); + } + + // Open shapes. + { + Point p(100, 100); + Path path = PathBuilder{}.AddLine(p, {200, 100}).TakePath(); + ContourComponent contour; + path.GetContourComponentAtIndex(0, contour); + ASSERT_POINT_NEAR(contour.destination, p); + ASSERT_FALSE(contour.is_closed); + } + + { + Path path = + PathBuilder{} + .AddCubicCurve({100, 100}, {100, 50}, {100, 150}, {200, 100}) + .TakePath(); + ContourComponent contour; + path.GetContourComponentAtIndex(0, contour); + ASSERT_POINT_NEAR(contour.destination, Point(100, 100)); + ASSERT_FALSE(contour.is_closed); + } + + { + Path path = PathBuilder{} + .AddQuadraticCurve({100, 100}, {100, 50}, {200, 100}) + .TakePath(); + ContourComponent contour; + path.GetContourComponentAtIndex(0, contour); + ASSERT_POINT_NEAR(contour.destination, Point(100, 100)); + ASSERT_FALSE(contour.is_closed); + } +} + +TEST(PathTest, PathCreatePolylineGeneratesCorrectContourData) { + Path::Polyline polyline = PathBuilder{} + .AddLine({100, 100}, {200, 100}) + .MoveTo({100, 200}) + .LineTo({150, 250}) + .LineTo({200, 200}) + .Close() + .TakePath() + .CreatePolyline(1.0f); + ASSERT_EQ(polyline.points->size(), 6u); + ASSERT_EQ(polyline.contours.size(), 2u); + ASSERT_EQ(polyline.contours[0].is_closed, false); + ASSERT_EQ(polyline.contours[0].start_index, 0u); + ASSERT_EQ(polyline.contours[1].is_closed, true); + ASSERT_EQ(polyline.contours[1].start_index, 2u); +} + +TEST(PathTest, PolylineGetContourPointBoundsReturnsCorrectRanges) { + Path::Polyline polyline = PathBuilder{} + .AddLine({100, 100}, {200, 100}) + .MoveTo({100, 200}) + .LineTo({150, 250}) + .LineTo({200, 200}) + .Close() + .TakePath() + .CreatePolyline(1.0f); + size_t a1, a2, b1, b2; + std::tie(a1, a2) = polyline.GetContourPointBounds(0); + std::tie(b1, b2) = polyline.GetContourPointBounds(1); + ASSERT_EQ(a1, 0u); + ASSERT_EQ(a2, 2u); + ASSERT_EQ(b1, 2u); + ASSERT_EQ(b2, 6u); +} + +TEST(PathTest, PathAddRectPolylineHasCorrectContourData) { + Path::Polyline polyline = PathBuilder{} + .AddRect(Rect::MakeLTRB(50, 60, 70, 80)) + .TakePath() + .CreatePolyline(1.0f); + ASSERT_EQ(polyline.contours.size(), 1u); + ASSERT_TRUE(polyline.contours[0].is_closed); + ASSERT_EQ(polyline.contours[0].start_index, 0u); + ASSERT_EQ(polyline.points->size(), 5u); + ASSERT_EQ(polyline.GetPoint(0), Point(50, 60)); + ASSERT_EQ(polyline.GetPoint(1), Point(70, 60)); + ASSERT_EQ(polyline.GetPoint(2), Point(70, 80)); + ASSERT_EQ(polyline.GetPoint(3), Point(50, 80)); + ASSERT_EQ(polyline.GetPoint(4), Point(50, 60)); +} + +TEST(PathTest, PathPolylineDuplicatesAreRemovedForSameContour) { + Path::Polyline polyline = + PathBuilder{} + .MoveTo({50, 50}) + .LineTo({50, 50}) // Insert duplicate at beginning of contour. + .LineTo({100, 50}) + .LineTo({100, 50}) // Insert duplicate at contour join. + .LineTo({100, 100}) + .Close() // Implicitly insert duplicate {50, 50} across contours. + .LineTo({0, 50}) + .LineTo({0, 100}) + .LineTo({0, 100}) // Insert duplicate at end of contour. + .TakePath() + .CreatePolyline(1.0f); + ASSERT_EQ(polyline.contours.size(), 2u); + ASSERT_EQ(polyline.contours[0].start_index, 0u); + ASSERT_TRUE(polyline.contours[0].is_closed); + ASSERT_EQ(polyline.contours[1].start_index, 4u); + ASSERT_FALSE(polyline.contours[1].is_closed); + ASSERT_EQ(polyline.points->size(), 7u); + ASSERT_EQ(polyline.GetPoint(0), Point(50, 50)); + ASSERT_EQ(polyline.GetPoint(1), Point(100, 50)); + ASSERT_EQ(polyline.GetPoint(2), Point(100, 100)); + ASSERT_EQ(polyline.GetPoint(3), Point(50, 50)); + ASSERT_EQ(polyline.GetPoint(4), Point(50, 50)); + ASSERT_EQ(polyline.GetPoint(5), Point(0, 50)); + ASSERT_EQ(polyline.GetPoint(6), Point(0, 100)); +} + +TEST(PathTest, PolylineBufferReuse) { + auto point_buffer = std::make_unique>(); + auto point_buffer_address = reinterpret_cast(point_buffer.get()); + Path::Polyline polyline = + PathBuilder{} + .MoveTo({50, 50}) + .LineTo({100, 100}) + .TakePath() + .CreatePolyline( + 1.0f, std::move(point_buffer), + [point_buffer_address]( + Path::Polyline::PointBufferPtr point_buffer) { + ASSERT_EQ(point_buffer->size(), 0u); + ASSERT_EQ(point_buffer_address, + reinterpret_cast(point_buffer.get())); + }); +} + +TEST(PathTest, PolylineFailsWithNullptrBuffer) { + EXPECT_DEATH_IF_SUPPORTED(PathBuilder{} + .MoveTo({50, 50}) + .LineTo({100, 100}) + .TakePath() + .CreatePolyline(1.0f, nullptr), + ""); +} + +TEST(PathTest, PathShifting) { + PathBuilder builder{}; + auto path = + builder.AddLine(Point(0, 0), Point(10, 10)) + .AddQuadraticCurve(Point(10, 10), Point(15, 15), Point(20, 20)) + .AddCubicCurve(Point(20, 20), Point(25, 25), Point(-5, -5), + Point(30, 30)) + .Close() + .Shift(Point(1, 1)) + .TakePath(); + + ContourComponent contour; + LinearPathComponent linear; + QuadraticPathComponent quad; + CubicPathComponent cubic; + + ASSERT_TRUE(path.GetContourComponentAtIndex(0, contour)); + ASSERT_TRUE(path.GetLinearComponentAtIndex(1, linear)); + ASSERT_TRUE(path.GetQuadraticComponentAtIndex(3, quad)); + ASSERT_TRUE(path.GetCubicComponentAtIndex(5, cubic)); + + EXPECT_EQ(contour.destination, Point(1, 1)); + + EXPECT_EQ(linear.p1, Point(1, 1)); + EXPECT_EQ(linear.p2, Point(11, 11)); + + EXPECT_EQ(quad.cp, Point(16, 16)); + EXPECT_EQ(quad.p1, Point(11, 11)); + EXPECT_EQ(quad.p2, Point(21, 21)); + + EXPECT_EQ(cubic.cp1, Point(26, 26)); + EXPECT_EQ(cubic.cp2, Point(-4, -4)); + EXPECT_EQ(cubic.p1, Point(21, 21)); + EXPECT_EQ(cubic.p2, Point(31, 31)); +} + +TEST(PathTest, PathBuilderWillComputeBounds) { + PathBuilder builder; + auto path_1 = builder.AddLine({0, 0}, {1, 1}).TakePath(); + + ASSERT_EQ(path_1.GetBoundingBox().value_or(Rect::MakeMaximum()), + Rect::MakeLTRB(0, 0, 1, 1)); + + auto path_2 = builder.AddLine({-1, -1}, {1, 1}).TakePath(); + + // Verify that PathBuilder recomputes the bounds. + ASSERT_EQ(path_2.GetBoundingBox().value_or(Rect::MakeMaximum()), + Rect::MakeLTRB(-1, -1, 1, 1)); + + // PathBuilder can set the bounds to whatever it wants + auto path_3 = builder.AddLine({0, 0}, {1, 1}) + .SetBounds(Rect::MakeLTRB(0, 0, 100, 100)) + .TakePath(); + + ASSERT_EQ(path_3.GetBoundingBox().value_or(Rect::MakeMaximum()), + Rect::MakeLTRB(0, 0, 100, 100)); +} + +TEST(PathTest, PathHorizontalLine) { + PathBuilder builder; + auto path = builder.HorizontalLineTo(10).TakePath(); + + LinearPathComponent linear; + path.GetLinearComponentAtIndex(1, linear); + + EXPECT_EQ(linear.p1, Point(0, 0)); + EXPECT_EQ(linear.p2, Point(10, 0)); +} + +TEST(PathTest, PathVerticalLine) { + PathBuilder builder; + auto path = builder.VerticalLineTo(10).TakePath(); + + LinearPathComponent linear; + path.GetLinearComponentAtIndex(1, linear); + + EXPECT_EQ(linear.p1, Point(0, 0)); + EXPECT_EQ(linear.p2, Point(0, 10)); +} + +TEST(PathTest, QuadradicPath) { + PathBuilder builder; + auto path = builder.QuadraticCurveTo(Point(10, 10), Point(20, 20)).TakePath(); + + QuadraticPathComponent quad; + path.GetQuadraticComponentAtIndex(1, quad); + + EXPECT_EQ(quad.p1, Point(0, 0)); + EXPECT_EQ(quad.cp, Point(10, 10)); + EXPECT_EQ(quad.p2, Point(20, 20)); +} + +TEST(PathTest, CubicPath) { + PathBuilder builder; + auto path = + builder.CubicCurveTo(Point(10, 10), Point(-10, -10), Point(20, 20)) + .TakePath(); + + CubicPathComponent cubic; + path.GetCubicComponentAtIndex(1, cubic); + + EXPECT_EQ(cubic.p1, Point(0, 0)); + EXPECT_EQ(cubic.cp1, Point(10, 10)); + EXPECT_EQ(cubic.cp2, Point(-10, -10)); + EXPECT_EQ(cubic.p2, Point(20, 20)); +} + +TEST(PathTest, BoundingBoxCubic) { + PathBuilder builder; + auto path = + builder.AddCubicCurve({120, 160}, {25, 200}, {220, 260}, {220, 40}) + .TakePath(); + auto box = path.GetBoundingBox(); + Rect expected = Rect::MakeXYWH(93.9101, 40, 126.09, 158.862); + ASSERT_TRUE(box.has_value()); + ASSERT_RECT_NEAR(box.value_or(Rect::MakeMaximum()), expected); +} + +TEST(PathTest, BoundingBoxOfCompositePathIsCorrect) { + PathBuilder builder; + builder.AddRoundedRect(Rect::MakeXYWH(10, 10, 300, 300), {50, 50, 50, 50}); + auto path = builder.TakePath(); + auto actual = path.GetBoundingBox(); + Rect expected = Rect::MakeXYWH(10, 10, 300, 300); + + ASSERT_TRUE(actual.has_value()); + ASSERT_RECT_NEAR(actual.value_or(Rect::MakeMaximum()), expected); +} + +TEST(PathTest, ExtremaOfCubicPathComponentIsCorrect) { + CubicPathComponent cubic{{11.769268, 252.883148}, + {-6.2857933, 204.356461}, + {-4.53997231, 156.552902}, + {17.0067291, 109.472488}}; + auto points = cubic.Extrema(); + + ASSERT_EQ(points.size(), static_cast(3)); + ASSERT_POINT_NEAR(points[2], cubic.Solve(0.455916)); +} + +TEST(PathTest, PathGetBoundingBoxForCubicWithNoDerivativeRootsIsCorrect) { + PathBuilder builder; + // Straight diagonal line. + builder.AddCubicCurve({0, 1}, {2, 3}, {4, 5}, {6, 7}); + auto path = builder.TakePath(); + auto actual = path.GetBoundingBox(); + auto expected = Rect::MakeLTRB(0, 1, 6, 7); + + ASSERT_TRUE(actual.has_value()); + ASSERT_RECT_NEAR(actual.value_or(Rect::MakeMaximum()), expected); +} + +TEST(PathTest, EmptyPath) { + auto path = PathBuilder{}.TakePath(); + ASSERT_EQ(path.GetComponentCount(), 1u); + + ContourComponent c; + path.GetContourComponentAtIndex(0, c); + ASSERT_POINT_NEAR(c.destination, Point()); + + Path::Polyline polyline = path.CreatePolyline(1.0f); + ASSERT_TRUE(polyline.points->empty()); + ASSERT_TRUE(polyline.contours.empty()); +} + +TEST(PathTest, SimplePath) { + PathBuilder builder; + + auto path = builder.AddLine({0, 0}, {100, 100}) + .AddQuadraticCurve({100, 100}, {200, 200}, {300, 300}) + .AddCubicCurve({300, 300}, {400, 400}, {500, 500}, {600, 600}) + .TakePath(); + + ASSERT_EQ(path.GetComponentCount(), 6u); + ASSERT_EQ(path.GetComponentCount(Path::ComponentType::kLinear), 1u); + ASSERT_EQ(path.GetComponentCount(Path::ComponentType::kQuadratic), 1u); + ASSERT_EQ(path.GetComponentCount(Path::ComponentType::kCubic), 1u); + ASSERT_EQ(path.GetComponentCount(Path::ComponentType::kContour), 3u); + + path.EnumerateComponents( + [](size_t index, const LinearPathComponent& linear) { + Point p1(0, 0); + Point p2(100, 100); + ASSERT_EQ(index, 1u); + ASSERT_EQ(linear.p1, p1); + ASSERT_EQ(linear.p2, p2); + }, + [](size_t index, const QuadraticPathComponent& quad) { + Point p1(100, 100); + Point cp(200, 200); + Point p2(300, 300); + ASSERT_EQ(index, 3u); + ASSERT_EQ(quad.p1, p1); + ASSERT_EQ(quad.cp, cp); + ASSERT_EQ(quad.p2, p2); + }, + [](size_t index, const CubicPathComponent& cubic) { + Point p1(300, 300); + Point cp1(400, 400); + Point cp2(500, 500); + Point p2(600, 600); + ASSERT_EQ(index, 5u); + ASSERT_EQ(cubic.p1, p1); + ASSERT_EQ(cubic.cp1, cp1); + ASSERT_EQ(cubic.cp2, cp2); + ASSERT_EQ(cubic.p2, p2); + }, + [](size_t index, const ContourComponent& contour) { + // There is an initial countour added for each curve. + if (index == 0u) { + Point p1(0, 0); + ASSERT_EQ(contour.destination, p1); + } else if (index == 2u) { + Point p1(100, 100); + ASSERT_EQ(contour.destination, p1); + } else if (index == 4u) { + Point p1(300, 300); + ASSERT_EQ(contour.destination, p1); + } else { + ASSERT_FALSE(true); + } + ASSERT_FALSE(contour.is_closed); + }); +} + +} // namespace testing +} // namespace impeller