diff --git a/impeller/aiks/aiks_unittests.cc b/impeller/aiks/aiks_unittests.cc index 14d70f2d27f7f..1f10479df403b 100644 --- a/impeller/aiks/aiks_unittests.cc +++ b/impeller/aiks/aiks_unittests.cc @@ -274,6 +274,16 @@ TEST_P(AiksTest, CanRenderCurvedStrokes) { ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); } +TEST_P(AiksTest, CanRenderThickCurvedStrokes) { + Canvas canvas; + Paint paint; + paint.color = Color::Red(); + paint.stroke_width = 100.0; + paint.style = Paint::Style::kStroke; + canvas.DrawPath(PathBuilder{}.AddCircle({100, 100}, 50).TakePath(), paint); + ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); +} + TEST_P(AiksTest, CanRenderClips) { Canvas canvas; Paint paint; @@ -1233,6 +1243,37 @@ TEST_P(AiksTest, CanRenderRoundedRectWithNonUniformRadii) { ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); } +TEST_P(AiksTest, CanRenderStrokePathThatEndsAtSharpTurn) { + Canvas canvas; + + Paint paint; + paint.color = Color::Red(); + paint.style = Paint::Style::kStroke; + paint.stroke_width = 200; + + Rect rect = {100, 100, 200, 200}; + PathBuilder builder; + builder.AddArc(rect, Degrees(0), Degrees(90), false); + + canvas.DrawPath(builder.TakePath(), paint); + ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); +} + +TEST_P(AiksTest, CanRenderStrokePathWithCubicLine) { + Canvas canvas; + + Paint paint; + paint.color = Color::Red(); + paint.style = Paint::Style::kStroke; + paint.stroke_width = 20; + + PathBuilder builder; + builder.AddCubicCurve({0, 200}, {50, 400}, {350, 0}, {400, 200}); + + canvas.DrawPath(builder.TakePath(), paint); + ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); +} + TEST_P(AiksTest, CanRenderDifferencePaths) { Canvas canvas; @@ -2064,6 +2105,22 @@ TEST_P(AiksTest, DrawRectStrokesRenderCorrectly) { ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); } +TEST_P(AiksTest, DrawRectStrokesWithBevelJoinRenderCorrectly) { + Canvas canvas; + Paint paint; + paint.color = Color::Red(); + paint.style = Paint::Style::kStroke; + paint.stroke_width = 10; + paint.stroke_join = Join::kBevel; + + canvas.Translate({100, 100}); + canvas.DrawPath( + PathBuilder{}.AddRect(Rect::MakeSize(Size{100, 100})).TakePath(), + {paint}); + + ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); +} + TEST_P(AiksTest, SaveLayerDrawsBehindSubsequentEntities) { // Compare with https://fiddle.skia.org/c/9e03de8567ffb49e7e83f53b64bcf636 Canvas canvas; diff --git a/impeller/core/formats.h b/impeller/core/formats.h index 6cc151e81939c..0e8d0d7d86a43 100644 --- a/impeller/core/formats.h +++ b/impeller/core/formats.h @@ -325,11 +325,33 @@ enum class IndexType { kNone, }; +/// Decides how backend draws pixels based on input vertices. enum class PrimitiveType { + /// Draws a triage for each separate set of three vertices. + /// + /// Vertices [A, B, C, D, E, F] will produce triages + /// [ABC, DEF]. kTriangle, + + /// Draws a triage for every adjacent three vertices. + /// + /// Vertices [A, B, C, D, E, F] will produce triages + /// [ABC, BCD, CDE, DEF]. kTriangleStrip, + + /// Draws a line for each separate set of two vertices. + /// + /// Vertices [A, B, C] will produce discontinued line + /// [AB, BC]. kLine, + + /// Draws a continuous line that connect every input vertices + /// + /// Vertices [A, B, C] will produce one continuous line + /// [ABC]. kLineStrip, + + /// Draws a point at each input vertex. kPoint, // Triangle fans are implementation dependent and need extra extensions // checks. Hence, they are not supported here. diff --git a/impeller/entity/geometry/stroke_path_geometry.cc b/impeller/entity/geometry/stroke_path_geometry.cc index 43648a548e29e..144fddcef1340 100644 --- a/impeller/entity/geometry/stroke_path_geometry.cc +++ b/impeller/entity/geometry/stroke_path_geometry.cc @@ -242,14 +242,99 @@ StrokePathGeometry::CreateSolidStrokeVertices( Point offset; Point previous_offset; // Used for computing joins. - auto compute_offset = [&polyline, &offset, &previous_offset, - &stroke_width](size_t point_i) { + // Computes offset by calculating the direction from point_i - 1 to point_i if + // point_i is within `contour_start_point_i` and `contour_end_point_i`; + // Otherwise, it uses direction from contour. + auto compute_offset = [&polyline, &offset, &previous_offset, &stroke_width]( + const size_t point_i, + const size_t contour_start_point_i, + const size_t contour_end_point_i, + const Path::PolylineContour& contour) { + Point direction; + if (point_i >= contour_end_point_i) { + direction = contour.end_direction; + } else if (point_i <= contour_start_point_i) { + direction = -contour.start_direction; + } else { + direction = + (polyline.points[point_i] - polyline.points[point_i - 1]).Normalize(); + } previous_offset = offset; - Point direction = - (polyline.points[point_i] - polyline.points[point_i - 1]).Normalize(); offset = Vector2{-direction.y, direction.x} * stroke_width * 0.5; }; + auto add_vertices_for_linear_component = + [&vtx_builder, &offset, &previous_offset, &vtx, &polyline, + &compute_offset, scaled_miter_limit, scale, &join_proc]( + const size_t component_start_index, const size_t component_end_index, + const size_t contour_start_point_i, const size_t contour_end_point_i, + const Path::PolylineContour& contour) { + auto is_last_component = + component_start_index == + contour.components.back().component_start_index; + + for (size_t point_i = component_start_index; + point_i < component_end_index; point_i++) { + auto is_end_of_component = point_i == component_end_index - 1; + vtx.position = polyline.points[point_i] + offset; + vtx_builder.AppendVertex(vtx); + vtx.position = polyline.points[point_i] - offset; + vtx_builder.AppendVertex(vtx); + + // For line components, two additional points need to be appended + // prior to appending a join connecting the next component. + vtx.position = polyline.points[point_i + 1] + offset; + vtx_builder.AppendVertex(vtx); + vtx.position = polyline.points[point_i + 1] - offset; + vtx_builder.AppendVertex(vtx); + + compute_offset(point_i + 2, contour_start_point_i, + contour_end_point_i, contour); + if (!is_last_component && is_end_of_component) { + // Generate join from the current line to the next line. + join_proc(vtx_builder, polyline.points[point_i + 1], + previous_offset, offset, scaled_miter_limit, scale); + } + } + }; + + auto add_vertices_for_curve_component = + [&vtx_builder, &offset, &previous_offset, &vtx, &polyline, + &compute_offset, scaled_miter_limit, scale, &join_proc]( + const size_t component_start_index, const size_t component_end_index, + const size_t contour_start_point_i, const size_t contour_end_point_i, + const Path::PolylineContour& contour) { + auto is_last_component = + component_start_index == + contour.components.back().component_start_index; + + for (size_t point_i = component_start_index; + point_i < component_end_index; point_i++) { + auto is_end_of_component = point_i == component_end_index - 1; + + vtx.position = polyline.points[point_i] + offset; + vtx_builder.AppendVertex(vtx); + vtx.position = polyline.points[point_i] - offset; + vtx_builder.AppendVertex(vtx); + + compute_offset(point_i + 2, contour_start_point_i, + contour_end_point_i, contour); + // For curve components, the polyline is detailed enough such that + // it can avoid worrying about joins altogether. + if (is_end_of_component) { + vtx.position = polyline.points[point_i + 1] + offset; + vtx_builder.AppendVertex(vtx); + vtx.position = polyline.points[point_i + 1] - offset; + vtx_builder.AppendVertex(vtx); + // Generate join from the current line to the next line. + if (!is_last_component) { + join_proc(vtx_builder, polyline.points[point_i + 1], + previous_offset, offset, scaled_miter_limit, scale); + } + } + } + }; + for (size_t contour_i = 0; contour_i < polyline.contours.size(); contour_i++) { auto contour = polyline.contours[contour_i]; @@ -270,8 +355,8 @@ StrokePathGeometry::CreateSolidStrokeVertices( break; } - // The first point's offset is always the same as the second point. - compute_offset(contour_start_point_i + 1); + compute_offset(contour_start_point_i, contour_start_point_i, + contour_end_point_i, contour); const Point contour_first_offset = offset; if (contour_i > 0) { @@ -305,30 +390,31 @@ StrokePathGeometry::CreateSolidStrokeVertices( scale, true); } - // Generate contour geometry. - for (size_t point_i = contour_start_point_i + 1; - point_i < contour_end_point_i; point_i++) { - // Generate line rect. - vtx.position = polyline.points[point_i - 1] + offset; - vtx_builder.AppendVertex(vtx); - vtx.position = polyline.points[point_i - 1] - offset; - vtx_builder.AppendVertex(vtx); - vtx.position = polyline.points[point_i] + offset; - vtx_builder.AppendVertex(vtx); - vtx.position = polyline.points[point_i] - offset; - vtx_builder.AppendVertex(vtx); - - if (point_i < contour_end_point_i - 1) { - compute_offset(point_i + 1); - - // Generate join from the current line to the next line. - join_proc(vtx_builder, polyline.points[point_i], previous_offset, - offset, scaled_miter_limit, scale); + for (size_t contour_component_i = 0; + contour_component_i < contour.components.size(); + contour_component_i++) { + auto component = contour.components[contour_component_i]; + auto is_last_component = + contour_component_i == contour.components.size() - 1; + + auto component_start_index = component.component_start_index; + auto component_end_index = + is_last_component ? contour_end_point_i - 1 + : contour.components[contour_component_i + 1] + .component_start_index; + if (component.is_curve) { + add_vertices_for_curve_component( + component_start_index, component_end_index, contour_start_point_i, + contour_end_point_i, contour); + } else { + add_vertices_for_linear_component( + component_start_index, component_end_index, contour_start_point_i, + contour_end_point_i, contour); } } // Generate end cap or join. - if (!polyline.contours[contour_i].is_closed) { + if (!contour.is_closed) { auto cap_offset = Vector2(-contour.end_direction.y, contour.end_direction.x) * stroke_width * 0.5; // Clockwise normal diff --git a/impeller/geometry/path.cc b/impeller/geometry/path.cc index ff2c39d6ecaf0..cfd770ad4a649 100644 --- a/impeller/geometry/path.cc +++ b/impeller/geometry/path.cc @@ -324,9 +324,10 @@ Path::Polyline Path::CreatePolyline(Scalar scale) const { return Vector2(0, -1); }; + std::vector components; std::optional previous_path_component_index; auto end_contour = [&polyline, &previous_path_component_index, - &get_path_component]() { + &get_path_component, &components]() { // Whenever a contour has ended, extract the exact end direction from the // last component. if (polyline.contours.empty()) { @@ -339,6 +340,8 @@ Path::Polyline Path::CreatePolyline(Scalar scale) const { auto& contour = polyline.contours.back(); contour.end_direction = Vector2(0, 1); + contour.components = components; + components.clear(); size_t previous_index = previous_path_component_index.value(); while (!std::holds_alternative( @@ -363,14 +366,26 @@ Path::Polyline Path::CreatePolyline(Scalar scale) const { const auto& component = components_[component_i]; switch (component.type) { case ComponentType::kLinear: + components.push_back({ + .component_start_index = polyline.points.size() - 1, + .is_curve = false, + }); collect_points(linears_[component.index].CreatePolyline()); previous_path_component_index = component_i; break; case ComponentType::kQuadratic: + components.push_back({ + .component_start_index = polyline.points.size() - 1, + .is_curve = true, + }); collect_points(quads_[component.index].CreatePolyline(scale)); previous_path_component_index = component_i; break; case ComponentType::kCubic: + components.push_back({ + .component_start_index = polyline.points.size() - 1, + .is_curve = true, + }); collect_points(cubics_[component.index].CreatePolyline(scale)); previous_path_component_index = component_i; break; @@ -386,13 +401,14 @@ Path::Polyline Path::CreatePolyline(Scalar scale) const { const auto& contour = contours_[component.index]; polyline.contours.push_back({.start_index = polyline.points.size(), .is_closed = contour.is_closed, - .start_direction = start_direction}); + .start_direction = start_direction, + .components = components}); previous_contour_point = std::nullopt; collect_points({contour.destination}); break; } - end_contour(); } + end_contour(); return polyline; } diff --git a/impeller/geometry/path.h b/impeller/geometry/path.h index 757c5c03d9548..bbbb207793d5f 100644 --- a/impeller/geometry/path.h +++ b/impeller/geometry/path.h @@ -61,8 +61,17 @@ class Path { }; struct PolylineContour { + struct Component { + size_t component_start_index; + /// Denotes whether this component is a curve. + /// + /// This is set to true when this component is generated from + /// QuadraticComponent or CubicPathComponent. + bool is_curve; + }; /// Index that denotes the first point of this contour. size_t start_index; + /// Denotes whether the last point of this contour is connected to the first /// point of this contour or not. bool is_closed; @@ -71,6 +80,12 @@ class Path { Vector2 start_direction; /// The direction of the contour's end cap. Vector2 end_direction; + + /// Distinct components in this contour. + /// + /// If this contour is generated from multiple path components, each + /// path component forms a component in this vector. + std::vector components; }; /// One or more contours represented as a series of points and indices in diff --git a/impeller/geometry/path_builder.cc b/impeller/geometry/path_builder.cc index 83359cc6b4452..da851dd24cbb8 100644 --- a/impeller/geometry/path_builder.cc +++ b/impeller/geometry/path_builder.cc @@ -187,9 +187,9 @@ PathBuilder& PathBuilder::AddRect(Rect rect) { auto tr = rect.origin + Point{rect.size.width, 0.0}; MoveTo(tl); - prototype_.AddLinearComponent(tl, tr) - .AddLinearComponent(tr, br) - .AddLinearComponent(br, bl); + LineTo(tr); + LineTo(br); + LineTo(bl); Close(); return *this; diff --git a/impeller/geometry/path_component.h b/impeller/geometry/path_component.h index c2a808cf18d2a..a8b0d4fa9bd68 100644 --- a/impeller/geometry/path_component.h +++ b/impeller/geometry/path_component.h @@ -47,9 +47,13 @@ struct LinearPathComponent { std::optional GetEndDirection() const; }; +// A component that represets a Quadratic Bézier curve. struct QuadraticPathComponent { + // Start point. Point p1; + // Control point. Point cp; + // End point. Point p2; QuadraticPathComponent() {} @@ -87,10 +91,15 @@ struct QuadraticPathComponent { std::optional GetEndDirection() const; }; +// A component that represets a Cubic Bézier curve. struct CubicPathComponent { + // Start point. Point p1; + // The first control point. Point cp1; + // The second control point. Point cp2; + // End point. Point p2; CubicPathComponent() {}