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

Commit 2a0b3ad

Browse files
zandersobdero
andauthored
[Impeller] Use analytical normals for stroke caps (#39124) (#39168)
Co-authored-by: Brandon DeRosier <[email protected]>
1 parent 5a73374 commit 2a0b3ad

File tree

6 files changed

+124
-11
lines changed

6 files changed

+124
-11
lines changed

impeller/display_list/display_list_unittests.cc

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,11 +108,13 @@ TEST_P(DisplayListTest, CanDrawArc) {
108108
auto callback = [&]() {
109109
static float start_angle = 45;
110110
static float sweep_angle = 270;
111+
static float stroke_width = 10;
111112
static bool use_center = true;
112113

113114
ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
114115
ImGui::SliderFloat("Start angle", &start_angle, -360, 360);
115116
ImGui::SliderFloat("Sweep angle", &sweep_angle, -360, 360);
117+
ImGui::SliderFloat("Stroke width", &stroke_width, 0, 100);
116118
ImGui::Checkbox("Use center", &use_center);
117119
ImGui::End();
118120

@@ -124,15 +126,15 @@ TEST_P(DisplayListTest, CanDrawArc) {
124126
Vector2 scale = GetContentScale();
125127
builder.scale(scale.x, scale.y);
126128
builder.setStyle(flutter::DlDrawStyle::kStroke);
127-
builder.setStrokeCap(flutter::DlStrokeCap::kRound);
129+
builder.setStrokeCap(flutter::DlStrokeCap::kButt);
128130
builder.setStrokeJoin(flutter::DlStrokeJoin::kMiter);
129131
builder.setStrokeMiter(10);
130132
auto rect = SkRect::MakeLTRB(p1.x, p1.y, p2.x, p2.y);
131133
builder.setColor(SK_ColorGREEN);
132134
builder.setStrokeWidth(2);
133135
builder.drawRect(rect);
134136
builder.setColor(SK_ColorRED);
135-
builder.setStrokeWidth(10);
137+
builder.setStrokeWidth(stroke_width);
136138
builder.drawArc(rect, start_angle, sweep_angle, use_center);
137139

138140
return builder.Build();

impeller/entity/geometry.cc

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,13 @@ StrokePathGeometry::CapProc StrokePathGeometry::GetCapProc(Cap stroke_cap) {
236236
case Cap::kButt:
237237
cap_proc = [](VertexBufferBuilder<VS::PerVertexData>& vtx_builder,
238238
const Point& position, const Point& offset,
239-
Scalar tolerance) {};
239+
Scalar tolerance) {
240+
VS::PerVertexData vtx;
241+
vtx.position = position + offset;
242+
vtx_builder.AppendVertex(vtx);
243+
vtx.position = position - offset;
244+
vtx_builder.AppendVertex(vtx);
245+
};
240246
break;
241247
case Cap::kRound:
242248
cap_proc = [](VertexBufferBuilder<VS::PerVertexData>& vtx_builder,
@@ -270,7 +276,6 @@ StrokePathGeometry::CapProc StrokePathGeometry::GetCapProc(Cap stroke_cap) {
270276
const Point& position, const Point& offset,
271277
Scalar tolerance) {
272278
VS::PerVertexData vtx;
273-
vtx.position = position;
274279

275280
Point forward(offset.y, -offset.x);
276281

@@ -316,6 +321,7 @@ VertexBuffer StrokePathGeometry::CreateSolidStrokeVertices(
316321

317322
for (size_t contour_i = 0; contour_i < polyline.contours.size();
318323
contour_i++) {
324+
auto contour = polyline.contours[contour_i];
319325
size_t contour_start_point_i, contour_end_point_i;
320326
std::tie(contour_start_point_i, contour_end_point_i) =
321327
polyline.GetContourPointBounds(contour_i);
@@ -361,7 +367,10 @@ VertexBuffer StrokePathGeometry::CreateSolidStrokeVertices(
361367

362368
// Generate start cap.
363369
if (!polyline.contours[contour_i].is_closed) {
364-
cap_proc(vtx_builder, polyline.points[contour_start_point_i], -offset,
370+
auto cap_offset =
371+
Vector2(contour.start_direction.y, -contour.start_direction.x) *
372+
stroke_width * 0.5;
373+
cap_proc(vtx_builder, polyline.points[contour_start_point_i], cap_offset,
365374
tolerance);
366375
}
367376

@@ -391,8 +400,11 @@ VertexBuffer StrokePathGeometry::CreateSolidStrokeVertices(
391400

392401
// Generate end cap or join.
393402
if (!polyline.contours[contour_i].is_closed) {
394-
cap_proc(vtx_builder, polyline.points[contour_end_point_i - 1], offset,
395-
tolerance);
403+
auto cap_offset =
404+
Vector2(-contour.end_direction.y, contour.end_direction.x) *
405+
stroke_width * 0.5;
406+
cap_proc(vtx_builder, polyline.points[contour_end_point_i - 1],
407+
cap_offset, tolerance);
396408
} else {
397409
join_proc(vtx_builder, polyline.points[contour_start_point_i], offset,
398410
contour_first_offset, scaled_miter_limit, tolerance);

impeller/geometry/path.cc

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,32 +242,81 @@ Path::Polyline Path::CreatePolyline(Scalar tolerance) const {
242242
}
243243
};
244244

245+
auto get_path_component =
246+
[this](size_t component_i) -> std::optional<const PathComponent*> {
247+
if (component_i >= components_.size()) {
248+
return std::nullopt;
249+
}
250+
const auto& component = components_[component_i];
251+
switch (component.type) {
252+
case ComponentType::kLinear:
253+
return &linears_[component.index];
254+
case ComponentType::kQuadratic:
255+
return &quads_[component.index];
256+
case ComponentType::kCubic:
257+
return &cubics_[component.index];
258+
case ComponentType::kContour:
259+
return std::nullopt;
260+
}
261+
};
262+
263+
std::optional<const PathComponent*> previous_path_component;
264+
auto end_contour = [&polyline, &previous_path_component]() {
265+
// Whenever a contour has ended, extract the exact end direction from the
266+
// last component.
267+
if (polyline.contours.empty()) {
268+
return;
269+
}
270+
if (!previous_path_component.has_value()) {
271+
return;
272+
}
273+
auto& contour = polyline.contours.back();
274+
contour.end_direction =
275+
previous_path_component.value()->GetEndDirection().value_or(
276+
Vector2(0, 1));
277+
};
278+
245279
for (size_t component_i = 0; component_i < components_.size();
246280
component_i++) {
247281
const auto& component = components_[component_i];
248282
switch (component.type) {
249283
case ComponentType::kLinear:
250284
collect_points(linears_[component.index].CreatePolyline());
285+
previous_path_component = &linears_[component.index];
251286
break;
252287
case ComponentType::kQuadratic:
253288
collect_points(quads_[component.index].CreatePolyline(tolerance));
289+
previous_path_component = &quads_[component.index];
254290
break;
255291
case ComponentType::kCubic:
256292
collect_points(cubics_[component.index].CreatePolyline(tolerance));
293+
previous_path_component = &cubics_[component.index];
257294
break;
258295
case ComponentType::kContour:
259296
if (component_i == components_.size() - 1) {
260297
// If the last component is a contour, that means it's an empty
261298
// contour, so skip it.
262299
continue;
263300
}
301+
end_contour();
302+
303+
Vector2 start_direction(0, -1);
304+
auto first_component = get_path_component(component_i + 1);
305+
if (first_component.has_value()) {
306+
start_direction =
307+
first_component.value()->GetStartDirection().value_or(
308+
Vector2(0, -1));
309+
}
310+
264311
const auto& contour = contours_[component.index];
265312
polyline.contours.push_back({.start_index = polyline.points.size(),
266-
.is_closed = contour.is_closed});
313+
.is_closed = contour.is_closed,
314+
.start_direction = start_direction});
267315
previous_contour_point = std::nullopt;
268316
collect_points({contour.destination});
269317
break;
270318
}
319+
end_contour();
271320
}
272321
return polyline;
273322
}

impeller/geometry/path.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@ class Path {
4949
/// Denotes whether the last point of this contour is connected to the first
5050
/// point of this contour or not.
5151
bool is_closed;
52+
53+
/// The direction of the contour's start cap.
54+
Vector2 start_direction;
55+
/// The direction of the contour's end cap.
56+
Vector2 end_direction;
5257
};
5358

5459
/// One or more contours represented as a series of points and indices in

impeller/geometry/path_component.cc

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88

99
namespace impeller {
1010

11+
PathComponent::~PathComponent() = default;
12+
1113
/*
1214
* Based on: https://en.wikipedia.org/wiki/B%C3%A9zier_curve#Specific_cases
1315
*/
@@ -67,6 +69,14 @@ std::vector<Point> LinearPathComponent::Extrema() const {
6769
return {p1, p2};
6870
}
6971

72+
std::optional<Vector2> LinearPathComponent::GetStartDirection() const {
73+
return (p1 - p2).Normalize();
74+
}
75+
76+
std::optional<Vector2> LinearPathComponent::GetEndDirection() const {
77+
return (p2 - p1).Normalize();
78+
}
79+
7080
Point QuadraticPathComponent::Solve(Scalar time) const {
7181
return {
7282
QuadraticSolve(time, p1.x, cp.x, p2.x), // x
@@ -139,6 +149,14 @@ std::vector<Point> QuadraticPathComponent::Extrema() const {
139149
return elevated.Extrema();
140150
}
141151

152+
std::optional<Vector2> QuadraticPathComponent::GetStartDirection() const {
153+
return (p1 - cp).Normalize();
154+
}
155+
156+
std::optional<Vector2> QuadraticPathComponent::GetEndDirection() const {
157+
return (p2 - cp).Normalize();
158+
}
159+
142160
Point CubicPathComponent::Solve(Scalar time) const {
143161
return {
144162
CubicSolve(time, p1.x, cp1.x, cp2.x, p2.x), // x
@@ -283,4 +301,12 @@ std::vector<Point> CubicPathComponent::Extrema() const {
283301
return points;
284302
}
285303

304+
std::optional<Vector2> CubicPathComponent::GetStartDirection() const {
305+
return (p1 - cp1).Normalize();
306+
}
307+
308+
std::optional<Vector2> CubicPathComponent::GetEndDirection() const {
309+
return (p2 - cp2).Normalize();
310+
}
311+
286312
} // namespace impeller

impeller/geometry/path_component.h

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,14 @@ namespace impeller {
2222
// points for the given scale.
2323
static constexpr Scalar kDefaultCurveTolerance = .1f;
2424

25-
struct LinearPathComponent {
25+
struct PathComponent {
26+
virtual ~PathComponent();
27+
28+
virtual std::optional<Vector2> GetStartDirection() const = 0;
29+
virtual std::optional<Vector2> GetEndDirection() const = 0;
30+
};
31+
32+
struct LinearPathComponent : public PathComponent {
2633
Point p1;
2734
Point p2;
2835

@@ -39,9 +46,13 @@ struct LinearPathComponent {
3946
bool operator==(const LinearPathComponent& other) const {
4047
return p1 == other.p1 && p2 == other.p2;
4148
}
49+
50+
std::optional<Vector2> GetStartDirection() const override;
51+
52+
std::optional<Vector2> GetEndDirection() const override;
4253
};
4354

44-
struct QuadraticPathComponent {
55+
struct QuadraticPathComponent : public PathComponent {
4556
Point p1;
4657
Point cp;
4758
Point p2;
@@ -76,9 +87,13 @@ struct QuadraticPathComponent {
7687
bool operator==(const QuadraticPathComponent& other) const {
7788
return p1 == other.p1 && cp == other.cp && p2 == other.p2;
7889
}
90+
91+
std::optional<Vector2> GetStartDirection() const override;
92+
93+
std::optional<Vector2> GetEndDirection() const override;
7994
};
8095

81-
struct CubicPathComponent {
96+
struct CubicPathComponent : public PathComponent {
8297
Point p1;
8398
Point cp1;
8499
Point cp2;
@@ -118,6 +133,10 @@ struct CubicPathComponent {
118133
p2 == other.p2;
119134
}
120135

136+
std::optional<Vector2> GetStartDirection() const override;
137+
138+
std::optional<Vector2> GetEndDirection() const override;
139+
121140
private:
122141
QuadraticPathComponent Lower() const;
123142
};

0 commit comments

Comments
 (0)