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

[Impeller] Use the start/end tangent of contours to compute stroke contour cap normals #39124

Merged
merged 1 commit into from
Jan 26, 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
6 changes: 4 additions & 2 deletions impeller/display_list/display_list_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand All @@ -125,15 +127,15 @@ 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);
builder.setColor(SK_ColorGREEN);
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();
Expand Down
22 changes: 17 additions & 5 deletions impeller/entity/geometry.cc
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,13 @@ StrokePathGeometry::CapProc StrokePathGeometry::GetCapProc(Cap stroke_cap) {
case Cap::kButt:
cap_proc = [](VertexBufferBuilder<VS::PerVertexData>& 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<VS::PerVertexData>& vtx_builder,
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -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);
Expand Down
51 changes: 50 additions & 1 deletion impeller/geometry/path.cc
Original file line number Diff line number Diff line change
Expand Up @@ -242,32 +242,81 @@ Path::Polyline Path::CreatePolyline(Scalar tolerance) const {
}
};

auto get_path_component =
[this](size_t component_i) -> std::optional<const PathComponent*> {
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<const PathComponent*> 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) {
// If the last component is a contour, that means it's an empty
// 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;
}
Expand Down
5 changes: 5 additions & 0 deletions impeller/geometry/path.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
26 changes: 26 additions & 0 deletions impeller/geometry/path_component.cc
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

namespace impeller {

PathComponent::~PathComponent() = default;

/*
* Based on: https://en.wikipedia.org/wiki/B%C3%A9zier_curve#Specific_cases
*/
Expand Down Expand Up @@ -67,6 +69,14 @@ std::vector<Point> LinearPathComponent::Extrema() const {
return {p1, p2};
}

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

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

Point QuadraticPathComponent::Solve(Scalar time) const {
return {
QuadraticSolve(time, p1.x, cp.x, p2.x), // x
Expand Down Expand Up @@ -139,6 +149,14 @@ std::vector<Point> QuadraticPathComponent::Extrema() const {
return elevated.Extrema();
}

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

std::optional<Vector2> 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
Expand Down Expand Up @@ -283,4 +301,12 @@ std::vector<Point> CubicPathComponent::Extrema() const {
return points;
}

std::optional<Vector2> CubicPathComponent::GetStartDirection() const {
return (p1 - cp1).Normalize();
}

std::optional<Vector2> CubicPathComponent::GetEndDirection() const {
return (p2 - cp2).Normalize();
}

} // namespace impeller
25 changes: 22 additions & 3 deletions impeller/geometry/path_component.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<Vector2> GetStartDirection() const = 0;
virtual std::optional<Vector2> GetEndDirection() const = 0;
};

struct LinearPathComponent : public PathComponent {
Point p1;
Point p2;

Expand All @@ -39,9 +46,13 @@ struct LinearPathComponent {
bool operator==(const LinearPathComponent& other) const {
return p1 == other.p1 && p2 == other.p2;
}

std::optional<Vector2> GetStartDirection() const override;

std::optional<Vector2> GetEndDirection() const override;
};

struct QuadraticPathComponent {
struct QuadraticPathComponent : public PathComponent {
Point p1;
Point cp;
Point p2;
Expand Down Expand Up @@ -76,9 +87,13 @@ struct QuadraticPathComponent {
bool operator==(const QuadraticPathComponent& other) const {
return p1 == other.p1 && cp == other.cp && p2 == other.p2;
}

std::optional<Vector2> GetStartDirection() const override;

std::optional<Vector2> GetEndDirection() const override;
};

struct CubicPathComponent {
struct CubicPathComponent : public PathComponent {
Point p1;
Point cp1;
Point cp2;
Expand Down Expand Up @@ -118,6 +133,10 @@ struct CubicPathComponent {
p2 == other.p2;
}

std::optional<Vector2> GetStartDirection() const override;

std::optional<Vector2> GetEndDirection() const override;

private:
QuadraticPathComponent Lower() const;
};
Expand Down