Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

[Impeller] Fix incorrect rendering path when duplicated point exists. #40115

Merged
merged 9 commits into from
Mar 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
194 changes: 159 additions & 35 deletions impeller/display_list/display_list_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -188,49 +188,173 @@ TEST_P(DisplayListTest, CanDrawArc) {
}

TEST_P(DisplayListTest, StrokedPathsDrawCorrectly) {
flutter::DisplayListBuilder builder;
builder.setColor(SK_ColorRED);
builder.setStyle(flutter::DlDrawStyle::kStroke);
builder.setStrokeWidth(10);
auto callback = [&]() {
flutter::DisplayListBuilder builder;
builder.setColor(SK_ColorRED);
builder.setStyle(flutter::DlDrawStyle::kStroke);

// Rectangle
builder.translate(100, 100);
builder.drawRect(SkRect::MakeSize({100, 100}));
static float stroke_width = 10.0f;
static int selected_stroke_type = 0;
static int selected_join_type = 0;
const char* stroke_types[] = {"Butte", "Round", "Square"};
const char* join_type[] = {"kMiter", "Round", "kBevel"};

// Rounded rectangle
builder.translate(150, 0);
builder.drawRRect(SkRRect::MakeRectXY(SkRect::MakeSize({100, 50}), 10, 10));
ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
ImGui::Combo("Cap", &selected_stroke_type, stroke_types,
sizeof(stroke_types) / sizeof(char*));
ImGui::Combo("Join", &selected_join_type, join_type,
sizeof(join_type) / sizeof(char*));
ImGui::SliderFloat("Stroke Width", &stroke_width, 10.0f, 50.0f);
ImGui::End();

// Double rounded rectangle
builder.translate(150, 0);
builder.drawDRRect(
SkRRect::MakeRectXY(SkRect::MakeSize({100, 50}), 10, 10),
SkRRect::MakeRectXY(SkRect::MakeXYWH(10, 10, 80, 30), 10, 10));
flutter::DlStrokeCap cap;
flutter::DlStrokeJoin join;
switch (selected_stroke_type) {
case 0:
cap = flutter::DlStrokeCap::kButt;
break;
case 1:
cap = flutter::DlStrokeCap::kRound;
break;
case 2:
cap = flutter::DlStrokeCap::kSquare;
break;
default:
cap = flutter::DlStrokeCap::kButt;
break;
}
switch (selected_join_type) {
case 0:
join = flutter::DlStrokeJoin::kMiter;
break;
case 1:
join = flutter::DlStrokeJoin::kRound;
break;
case 2:
join = flutter::DlStrokeJoin::kBevel;
break;
default:
join = flutter::DlStrokeJoin::kMiter;
break;
}
builder.setStrokeCap(cap);
builder.setStrokeJoin(join);
builder.setStrokeWidth(stroke_width);

// Contour with duplicate join points
{
// Make rendering better to watch.
builder.scale(1.5f, 1.5f);

// Rectangle
builder.translate(100, 100);
builder.drawRect(SkRect::MakeSize({100, 100}));

// Rounded rectangle
builder.translate(150, 0);
SkPath path;
path.lineTo({100, 0});
path.lineTo({100, 0});
path.lineTo({100, 100});
builder.drawPath(path);
}
builder.drawRRect(SkRRect::MakeRectXY(SkRect::MakeSize({100, 50}), 10, 10));

// Contour with duplicate end points
{
builder.setStrokeCap(flutter::DlStrokeCap::kRound);
// Double rounded rectangle
builder.translate(150, 0);
SkPath path;
path.moveTo(0, 0);
path.lineTo({0, 0});
path.lineTo({50, 50});
path.lineTo({100, 0});
path.lineTo({100, 0});
builder.drawPath(path);
}
builder.drawDRRect(
SkRRect::MakeRectXY(SkRect::MakeSize({100, 50}), 10, 10),
SkRRect::MakeRectXY(SkRect::MakeXYWH(10, 10, 80, 30), 10, 10));

ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
// Contour with duplicate join points
{
builder.translate(150, 0);
SkPath path;
path.moveTo(0, 0);
path.lineTo(0, 0);
path.lineTo({100, 0});
path.lineTo({100, 0});
path.lineTo({100, 100});
builder.drawPath(path);
}

// Contour with duplicate start and end points

// Line.
builder.translate(200, 0);
{
builder.save();

SkPath line_path;
line_path.moveTo(0, 0);
line_path.moveTo(0, 0);
line_path.lineTo({0, 0});
line_path.lineTo({0, 0});
line_path.lineTo({50, 50});
line_path.lineTo({50, 50});
line_path.lineTo({100, 0});
line_path.lineTo({100, 0});
builder.drawPath(line_path);

builder.translate(0, 100);
builder.drawPath(line_path);

builder.translate(0, 100);
SkPath line_path2;
line_path2.moveTo(0, 0);
line_path2.lineTo(0, 0);
line_path2.lineTo(0, 0);
builder.drawPath(line_path2);

builder.restore();
}

// Cubic.
builder.translate(150, 0);
{
builder.save();

SkPath cubic_path;
cubic_path.moveTo({0, 0});
cubic_path.cubicTo(0, 0, 140.0, 100.0, 140, 20);
builder.drawPath(cubic_path);

builder.translate(0, 100);
SkPath cubic_path2;
cubic_path2.moveTo({0, 0});
cubic_path2.cubicTo(0, 0, 0, 0, 150, 150);
builder.drawPath(cubic_path2);

builder.translate(0, 100);
SkPath cubic_path3;
cubic_path3.moveTo({0, 0});
cubic_path3.cubicTo(0, 0, 0, 0, 0, 0);
builder.drawPath(cubic_path3);

builder.restore();
}

// Quad.
builder.translate(200, 0);
{
builder.save();

SkPath quad_path;
quad_path.moveTo(0, 0);
quad_path.moveTo(0, 0);
quad_path.quadTo({100, 40}, {50, 80});
builder.drawPath(quad_path);

builder.translate(0, 150);
SkPath quad_path2;
quad_path2.moveTo(0, 0);
quad_path2.moveTo(0, 0);
quad_path2.quadTo({0, 0}, {100, 100});
builder.drawPath(quad_path2);

builder.translate(0, 100);
SkPath quad_path3;
quad_path3.moveTo(0, 0);
quad_path3.quadTo({0, 0}, {0, 0});
builder.drawPath(quad_path3);

builder.restore();
}
return builder.Build();
};
ASSERT_TRUE(OpenPlaygroundHere(callback));
}

TEST_P(DisplayListTest, CanDrawWithOddPathWinding) {
Expand Down
58 changes: 41 additions & 17 deletions impeller/geometry/path.cc
Original file line number Diff line number Diff line change
Expand Up @@ -261,20 +261,51 @@ Path::Polyline Path::CreatePolyline(Scalar scale) const {
}
};

std::optional<const PathComponent*> previous_path_component;
auto end_contour = [&polyline, &previous_path_component]() {
auto compute_contour_start_direction =
[&get_path_component](size_t current_path_component_index) {
size_t next_component_index = current_path_component_index + 1;
while (get_path_component(next_component_index).has_value()) {
auto next_component =
get_path_component(next_component_index).value();
if (next_component->GetStartDirection().has_value()) {
return next_component->GetStartDirection().value();
} else {
next_component_index++;
}
}
return Vector2(0, -1);
};

std::optional<size_t> previous_path_component_index;
auto end_contour = [&polyline, &previous_path_component_index,
&get_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()) {

if (!previous_path_component_index.has_value()) {
return;
}

auto& contour = polyline.contours.back();
contour.end_direction =
previous_path_component.value()->GetEndDirection().value_or(
Vector2(0, 1));
contour.end_direction = Vector2(0, 1);

size_t previous_index = previous_path_component_index.value();
while (get_path_component(previous_index).has_value()) {
auto previous_path_component = get_path_component(previous_index).value();
if (previous_path_component->GetEndDirection().has_value()) {
contour.end_direction =
previous_path_component->GetEndDirection().value();
break;
} else {
if (previous_index == 0) {
break;
}
previous_index--;
}
}
};

for (size_t component_i = 0; component_i < components_.size();
Expand All @@ -283,15 +314,15 @@ Path::Polyline Path::CreatePolyline(Scalar scale) const {
switch (component.type) {
case ComponentType::kLinear:
collect_points(linears_[component.index].CreatePolyline());
previous_path_component = &linears_[component.index];
previous_path_component_index = component_i;
break;
case ComponentType::kQuadratic:
collect_points(quads_[component.index].CreatePolyline(scale));
previous_path_component = &quads_[component.index];
previous_path_component_index = component_i;
break;
case ComponentType::kCubic:
collect_points(cubics_[component.index].CreatePolyline(scale));
previous_path_component = &cubics_[component.index];
previous_path_component_index = component_i;
break;
case ComponentType::kContour:
if (component_i == components_.size() - 1) {
Expand All @@ -301,14 +332,7 @@ Path::Polyline Path::CreatePolyline(Scalar scale) const {
}
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));
}

Vector2 start_direction = compute_contour_start_direction(component_i);
const auto& contour = contours_[component.index];
polyline.contours.push_back({.start_index = polyline.points.size(),
.is_closed = contour.is_closed,
Expand Down
44 changes: 40 additions & 4 deletions impeller/geometry/path_component.cc
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,16 @@ std::vector<Point> LinearPathComponent::Extrema() const {
}

std::optional<Vector2> LinearPathComponent::GetStartDirection() const {
if (p1 == p2) {
return std::nullopt;
}
return (p1 - p2).Normalize();
}

std::optional<Vector2> LinearPathComponent::GetEndDirection() const {
if (p1 == p2) {
return std::nullopt;
}
return (p2 - p1).Normalize();
}

Expand Down Expand Up @@ -150,11 +156,23 @@ std::vector<Point> QuadraticPathComponent::Extrema() const {
}

std::optional<Vector2> QuadraticPathComponent::GetStartDirection() const {
return (p1 - cp).Normalize();
if (p1 != cp) {
return (p1 - cp).Normalize();
}
if (p1 != p2) {
return (p1 - p2).Normalize();
}
return std::nullopt;
}

std::optional<Vector2> QuadraticPathComponent::GetEndDirection() const {
return (p2 - cp).Normalize();
if (p2 != cp) {
return (p2 - cp).Normalize();
}
if (p2 != p1) {
return (p2 - p1).Normalize();
}
return std::nullopt;
}

Point CubicPathComponent::Solve(Scalar time) const {
Expand Down Expand Up @@ -302,11 +320,29 @@ std::vector<Point> CubicPathComponent::Extrema() const {
}

std::optional<Vector2> CubicPathComponent::GetStartDirection() const {
return (p1 - cp1).Normalize();
if (p1 != cp1) {
return (p1 - cp1).Normalize();
}
if (p1 != cp2) {
return (p1 - cp2).Normalize();
}
if (p1 != p2) {
return (p1 - p2).Normalize();
}
return std::nullopt;
}

std::optional<Vector2> CubicPathComponent::GetEndDirection() const {
return (p2 - cp2).Normalize();
if (p2 != cp2) {
return (p2 - cp2).Normalize();
}
if (p2 != cp1) {
return (p2 - cp1).Normalize();
}
if (p2 != p1) {
return (p2 - p1).Normalize();
}
return std::nullopt;
}

} // namespace impeller