Skip to content

Commit 1a055b0

Browse files
bderodnfield
authored andcommitted
Add solid stroke coverage override (#127)
1 parent 0ed6d4c commit 1a055b0

File tree

3 files changed

+136
-53
lines changed

3 files changed

+136
-53
lines changed

impeller/entity/contents/solid_stroke_contents.cc

+24
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
#include "solid_stroke_contents.h"
66

7+
#include <optional>
8+
79
#include "impeller/entity/contents/clip_contents.h"
810
#include "impeller/entity/contents/content_context.h"
911
#include "impeller/entity/entity.h"
@@ -27,6 +29,28 @@ const Color& SolidStrokeContents::GetColor() const {
2729
return color_;
2830
}
2931

32+
std::optional<Rect> SolidStrokeContents::GetCoverage(
33+
const Entity& entity) const {
34+
auto path_coverage = entity.GetPathCoverage();
35+
if (!path_coverage.has_value()) {
36+
return std::nullopt;
37+
}
38+
39+
Scalar max_radius = 0.5;
40+
if (cap_ == Cap::kSquare) {
41+
max_radius = max_radius * kSqrt2;
42+
}
43+
if (join_ == Join::kMiter) {
44+
max_radius = std::max(max_radius, miter_limit_ * 0.5f);
45+
}
46+
Vector2 max_radius_xy = entity.GetTransformation().TransformDirection(
47+
Vector2(max_radius, max_radius) * stroke_size_);
48+
49+
return Rect(path_coverage->origin - max_radius_xy,
50+
Size(path_coverage->size.width + max_radius_xy.x * 2,
51+
path_coverage->size.height + max_radius_xy.y * 2));
52+
}
53+
3054
static VertexBuffer CreateSolidStrokeVertices(
3155
const Path& path,
3256
HostBuffer& buffer,

impeller/entity/contents/solid_stroke_contents.h

+3
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,9 @@ class SolidStrokeContents final : public Contents {
6868

6969
Join GetStrokeJoin();
7070

71+
// |Contents|
72+
std::optional<Rect> GetCoverage(const Entity& entity) const override;
73+
7174
// |Contents|
7275
bool Render(const ContentContext& renderer,
7376
const Entity& entity,

impeller/entity/entity_unittests.cc

+109-53
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include "impeller/entity/contents/solid_stroke_contents.h"
1313
#include "impeller/entity/entity.h"
1414
#include "impeller/entity/entity_playground.h"
15+
#include "impeller/geometry/geometry_unittests.h"
1516
#include "impeller/geometry/path_builder.h"
1617
#include "impeller/playground/playground.h"
1718
#include "impeller/playground/widgets.h"
@@ -100,76 +101,89 @@ TEST_F(EntityTest, StrokeCapAndJoinTest) {
100101
ImGui::SetNextWindowPos(
101102
{0 * padding.x + margin.x, 1.7f * padding.y + margin.y});
102103
}
103-
ImGui::Begin("Controls");
104+
104105
// Slightly above sqrt(2) by default, so that right angles are just below
105106
// the limit and acute angles are over the limit (causing them to get
106107
// beveled).
107108
static Scalar miter_limit = 1.41421357;
108109
static Scalar width = 30;
109-
ImGui::SliderFloat("Miter limit", &miter_limit, 0, 30);
110-
ImGui::SliderFloat("Stroke width", &width, 0, 100);
111-
if (ImGui::Button("Reset")) {
112-
miter_limit = 1.41421357;
113-
width = 30;
110+
111+
ImGui::Begin("Controls");
112+
{
113+
ImGui::SliderFloat("Miter limit", &miter_limit, 0, 30);
114+
ImGui::SliderFloat("Stroke width", &width, 0, 100);
115+
if (ImGui::Button("Reset")) {
116+
miter_limit = 1.41421357;
117+
width = 30;
118+
}
114119
}
115120
ImGui::End();
116121

117-
auto create_contents = [width = width](SolidStrokeContents::Cap cap,
118-
SolidStrokeContents::Join join) {
122+
auto render_path = [width = width, &context, &pass](
123+
Path path, SolidStrokeContents::Cap cap,
124+
SolidStrokeContents::Join join) {
119125
auto contents = std::make_unique<SolidStrokeContents>();
120126
contents->SetColor(Color::Red().Premultiply());
121127
contents->SetStrokeSize(width);
122128
contents->SetStrokeCap(cap);
123129
contents->SetStrokeJoin(join);
124130
contents->SetStrokeMiter(miter_limit);
125-
return contents;
126-
};
127131

128-
Entity entity;
132+
Entity entity;
133+
entity.SetPath(path);
134+
entity.SetContents(std::move(contents));
135+
136+
auto coverage = entity.GetCoverage();
137+
if (coverage.has_value()) {
138+
auto bounds_contents = std::make_unique<SolidColorContents>();
139+
bounds_contents->SetColor(Color::Green().WithAlpha(0.5));
140+
Entity bounds_entity;
141+
bounds_entity.SetPath(
142+
PathBuilder{}.AddRect(entity.GetCoverage().value()).TakePath());
143+
bounds_entity.SetContents(std::move(bounds_contents));
144+
bounds_entity.Render(context, pass);
145+
}
146+
147+
entity.Render(context, pass);
148+
};
129149

130150
const Point a_def(0, 0), b_def(0, 100), c_def(150, 0), d_def(150, -100),
131151
e_def(75, 75);
132152
const Scalar r = 30;
133153
// Cap::kButt demo.
134154
{
135155
Point off = Point(0, 0) * padding + margin;
136-
Point a, b, c, d;
137-
std::tie(a, b) = IMPELLER_PLAYGROUND_LINE(off + a_def, off + b_def, r,
138-
Color::Black(), Color::White());
139-
std::tie(c, d) = IMPELLER_PLAYGROUND_LINE(off + c_def, off + d_def, r,
140-
Color::Black(), Color::White());
141-
entity.SetPath(PathBuilder{}.AddCubicCurve(a, b, d, c).TakePath());
142-
entity.SetContents(create_contents(SolidStrokeContents::Cap::kButt,
143-
SolidStrokeContents::Join::kBevel));
144-
entity.Render(context, pass);
156+
auto [a, b] = IMPELLER_PLAYGROUND_LINE(off + a_def, off + b_def, r,
157+
Color::Black(), Color::White());
158+
auto [c, d] = IMPELLER_PLAYGROUND_LINE(off + c_def, off + d_def, r,
159+
Color::Black(), Color::White());
160+
render_path(PathBuilder{}.AddCubicCurve(a, b, d, c).TakePath(),
161+
SolidStrokeContents::Cap::kButt,
162+
SolidStrokeContents::Join::kBevel);
145163
}
146164

147165
// Cap::kSquare demo.
148166
{
149167
Point off = Point(1, 0) * padding + margin;
150-
Point a, b, c, d;
151-
std::tie(a, b) = IMPELLER_PLAYGROUND_LINE(off + a_def, off + b_def, r,
152-
Color::Black(), Color::White());
153-
std::tie(c, d) = IMPELLER_PLAYGROUND_LINE(off + c_def, off + d_def, r,
154-
Color::Black(), Color::White());
155-
entity.SetPath(PathBuilder{}.AddCubicCurve(a, b, d, c).TakePath());
156-
entity.SetContents(create_contents(SolidStrokeContents::Cap::kSquare,
157-
SolidStrokeContents::Join::kBevel));
158-
entity.Render(context, pass);
168+
auto [a, b] = IMPELLER_PLAYGROUND_LINE(off + a_def, off + b_def, r,
169+
Color::Black(), Color::White());
170+
auto [c, d] = IMPELLER_PLAYGROUND_LINE(off + c_def, off + d_def, r,
171+
Color::Black(), Color::White());
172+
render_path(PathBuilder{}.AddCubicCurve(a, b, d, c).TakePath(),
173+
SolidStrokeContents::Cap::kSquare,
174+
SolidStrokeContents::Join::kBevel);
159175
}
160176

161177
// Cap::kRound demo.
162178
{
163179
Point off = Point(2, 0) * padding + margin;
164-
Point a, b, c, d;
165-
std::tie(a, b) = IMPELLER_PLAYGROUND_LINE(off + a_def, off + b_def, r,
166-
Color::Black(), Color::White());
167-
std::tie(c, d) = IMPELLER_PLAYGROUND_LINE(off + c_def, off + d_def, r,
168-
Color::Black(), Color::White());
169-
entity.SetPath(PathBuilder{}.AddCubicCurve(a, b, d, c).TakePath());
170-
entity.SetContents(create_contents(SolidStrokeContents::Cap::kRound,
171-
SolidStrokeContents::Join::kBevel));
172-
entity.Render(context, pass);
180+
auto [a, b] = IMPELLER_PLAYGROUND_LINE(off + a_def, off + b_def, r,
181+
Color::Black(), Color::White());
182+
auto [c, d] = IMPELLER_PLAYGROUND_LINE(off + c_def, off + d_def, r,
183+
Color::Black(), Color::White());
184+
render_path(PathBuilder{}.AddCubicCurve(a, b, d, c).TakePath(),
185+
SolidStrokeContents::Cap::kRound,
186+
SolidStrokeContents::Join::kBevel);
173187
}
174188

175189
// Join::kBevel demo.
@@ -178,11 +192,9 @@ TEST_F(EntityTest, StrokeCapAndJoinTest) {
178192
Point a = IMPELLER_PLAYGROUND_POINT(off + a_def, r, Color::White());
179193
Point b = IMPELLER_PLAYGROUND_POINT(off + e_def, r, Color::White());
180194
Point c = IMPELLER_PLAYGROUND_POINT(off + c_def, r, Color::White());
181-
entity.SetPath(
182-
PathBuilder{}.MoveTo(a).LineTo(b).LineTo(c).Close().TakePath());
183-
entity.SetContents(create_contents(SolidStrokeContents::Cap::kButt,
184-
SolidStrokeContents::Join::kBevel));
185-
entity.Render(context, pass);
195+
render_path(
196+
PathBuilder{}.MoveTo(a).LineTo(b).LineTo(c).Close().TakePath(),
197+
SolidStrokeContents::Cap::kButt, SolidStrokeContents::Join::kBevel);
186198
}
187199

188200
// Join::kMiter demo.
@@ -191,11 +203,9 @@ TEST_F(EntityTest, StrokeCapAndJoinTest) {
191203
Point a = IMPELLER_PLAYGROUND_POINT(off + a_def, r, Color::White());
192204
Point b = IMPELLER_PLAYGROUND_POINT(off + e_def, r, Color::White());
193205
Point c = IMPELLER_PLAYGROUND_POINT(off + c_def, r, Color::White());
194-
entity.SetPath(
195-
PathBuilder{}.MoveTo(a).LineTo(b).LineTo(c).Close().TakePath());
196-
entity.SetContents(create_contents(SolidStrokeContents::Cap::kButt,
197-
SolidStrokeContents::Join::kMiter));
198-
entity.Render(context, pass);
206+
render_path(
207+
PathBuilder{}.MoveTo(a).LineTo(b).LineTo(c).Close().TakePath(),
208+
SolidStrokeContents::Cap::kButt, SolidStrokeContents::Join::kMiter);
199209
}
200210

201211
// Join::kRound demo.
@@ -204,11 +214,9 @@ TEST_F(EntityTest, StrokeCapAndJoinTest) {
204214
Point a = IMPELLER_PLAYGROUND_POINT(off + a_def, r, Color::White());
205215
Point b = IMPELLER_PLAYGROUND_POINT(off + e_def, r, Color::White());
206216
Point c = IMPELLER_PLAYGROUND_POINT(off + c_def, r, Color::White());
207-
entity.SetPath(
208-
PathBuilder{}.MoveTo(a).LineTo(b).LineTo(c).Close().TakePath());
209-
entity.SetContents(create_contents(SolidStrokeContents::Cap::kButt,
210-
SolidStrokeContents::Join::kRound));
211-
entity.Render(context, pass);
217+
render_path(
218+
PathBuilder{}.MoveTo(a).LineTo(b).LineTo(c).Close().TakePath(),
219+
SolidStrokeContents::Cap::kButt, SolidStrokeContents::Join::kRound);
212220
}
213221

214222
return true;
@@ -789,5 +797,53 @@ TEST_F(EntityTest, ContentsGetBoundsForEmptyPathReturnsNullopt) {
789797
ASSERT_FALSE(entity.GetPathCoverage().has_value());
790798
}
791799

800+
TEST_F(EntityTest, SolidStrokeCoverageIsCorrect) {
801+
{
802+
Entity entity;
803+
auto contents = std::make_unique<SolidStrokeContents>();
804+
contents->SetStrokeCap(SolidStrokeContents::Cap::kButt);
805+
contents->SetStrokeJoin(SolidStrokeContents::Join::kBevel);
806+
contents->SetStrokeSize(4);
807+
entity.SetPath(PathBuilder{}.AddLine({0, 0}, {10, 10}).TakePath());
808+
entity.SetContents(std::move(contents));
809+
auto actual = entity.GetCoverage();
810+
auto expected = Rect::MakeLTRB(-2, -2, 12, 12);
811+
ASSERT_TRUE(actual.has_value());
812+
ASSERT_RECT_NEAR(actual.value(), expected);
813+
}
814+
815+
// Cover the Cap::kSquare case.
816+
{
817+
Entity entity;
818+
auto contents = std::make_unique<SolidStrokeContents>();
819+
contents->SetStrokeCap(SolidStrokeContents::Cap::kSquare);
820+
contents->SetStrokeJoin(SolidStrokeContents::Join::kBevel);
821+
contents->SetStrokeSize(4);
822+
entity.SetPath(PathBuilder{}.AddLine({0, 0}, {10, 10}).TakePath());
823+
entity.SetContents(std::move(contents));
824+
auto actual = entity.GetCoverage();
825+
auto expected =
826+
Rect::MakeLTRB(-sqrt(8), -sqrt(8), 10 + sqrt(8), 10 + sqrt(8));
827+
ASSERT_TRUE(actual.has_value());
828+
ASSERT_RECT_NEAR(actual.value(), expected);
829+
}
830+
831+
// Cover the Join::kMiter case.
832+
{
833+
Entity entity;
834+
auto contents = std::make_unique<SolidStrokeContents>();
835+
contents->SetStrokeCap(SolidStrokeContents::Cap::kSquare);
836+
contents->SetStrokeJoin(SolidStrokeContents::Join::kMiter);
837+
contents->SetStrokeSize(4);
838+
contents->SetStrokeMiter(2);
839+
entity.SetPath(PathBuilder{}.AddLine({0, 0}, {10, 10}).TakePath());
840+
entity.SetContents(std::move(contents));
841+
auto actual = entity.GetCoverage();
842+
auto expected = Rect::MakeLTRB(-4, -4, 14, 14);
843+
ASSERT_TRUE(actual.has_value());
844+
ASSERT_RECT_NEAR(actual.value(), expected);
845+
}
846+
}
847+
792848
} // namespace testing
793849
} // namespace impeller

0 commit comments

Comments
 (0)