| Flutter Impeller |

path.cc
Go to the documentation of this file.
1 // Copyright 2013 The Flutter Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "impeller/geometry/path.h"
6
7 #include <optional>
8 #include <variant>
9
10 #include "flutter/fml/logging.h"
11 #include "impeller/geometry/path_component.h"
12 #include "impeller/geometry/point.h"
13
14 namespace impeller {
15
16 Path::Path() {
17 AddContourComponent({});
18 };
19
20 Path::~Path() = default;
21
22 std::tuple<size_t, size_t> Path::Polyline::GetContourPointBounds(
23 size_t contour_index) const {
24 if (contour_index >= contours.size()) {
25 return {points->size(), points->size()};
26 }
27 const size_t start_index = contours.at(contour_index).start_index;
28 const size_t end_index = (contour_index >= contours.size() - 1)
29 ? points->size()
30 : contours.at(contour_index + 1).start_index;
31 return std::make_tuple(start_index, end_index);
32 }
33
34 size_t Path::GetComponentCount(std::optional<ComponentType> type) const {
35 if (!type.has_value()) {
36 return components_.size();
37 }
38 auto type_value = type.value();
39 if (type_value == ComponentType::kContour) {
40 return contours_.size();
41 }
42 size_t count = 0u;
43 for (const auto& component : components_) {
44 if (component.type == type_value) {
45 count++;
46 }
47 }
48 return count;
49 }
50
51 void Path::SetFillType(FillType fill) {
52 fill_ = fill;
53 }
54
55 FillType Path::GetFillType() const {
56 return fill_;
57 }
58
59 bool Path::IsConvex() const {
60 return convexity_ == Convexity::kConvex;
61 }
62
63 void Path::SetConvexity(Convexity value) {
64 convexity_ = value;
65 }
66
67 void Path::Shift(Point shift) {
68 for (auto i = 0u; i < points_.size(); i++) {
69 points_[i] += shift;
70 }
71 for (auto& contour : contours_) {
72 contour.destination += shift;
73 }
74 }
75
76 Path Path::Clone() const {
77 Path new_path = *this;
78 return new_path;
79 }
80
81 Path& Path::AddLinearComponent(const Point& p1, const Point& p2) {
82 auto index = points_.size();
83 points_.emplace_back(p1);
84 points_.emplace_back(p2);
85 components_.emplace_back(ComponentType::kLinear, index);
86 return *this;
87 }
88
89 Path& Path::AddQuadraticComponent(const Point& p1,
90 const Point& cp,
91 const Point& p2) {
92 auto index = points_.size();
93 points_.emplace_back(p1);
94 points_.emplace_back(cp);
95 points_.emplace_back(p2);
96 components_.emplace_back(ComponentType::kQuadratic, index);
97 return *this;
98 }
99
100 Path& Path::AddCubicComponent(const Point& p1,
101 const Point& cp1,
102 const Point& cp2,
103 const Point& p2) {
104 auto index = points_.size();
105 points_.emplace_back(p1);
106 points_.emplace_back(cp1);
107 points_.emplace_back(cp2);
108 points_.emplace_back(p2);
109 components_.emplace_back(ComponentType::kCubic, index);
110 return *this;
111 }
112
113 Path& Path::AddContourComponent(const Point& destination, bool is_closed) {
114 if (components_.size() > 0 &&
115 components_.back().type == ComponentType::kContour) {
116 // Never insert contiguous contours.
117 contours_.back() = ContourComponent(destination, is_closed);
118 } else {
119 contours_.emplace_back(ContourComponent(destination, is_closed));
120 components_.emplace_back(ComponentType::kContour, contours_.size() - 1);
121 }
122 return *this;
123 }
124
125 void Path::SetContourClosed(bool is_closed) {
126 contours_.back().is_closed = is_closed;
127 }
128
129 void Path::EnumerateComponents(
130 const Applier<LinearPathComponent>& linear_applier,
131 const Applier<QuadraticPathComponent>& quad_applier,
132 const Applier<CubicPathComponent>& cubic_applier,
133 const Applier<ContourComponent>& contour_applier) const {
134 size_t currentIndex = 0;
135 for (const auto& component : components_) {
136 switch (component.type) {
137 case ComponentType::kLinear:
138 if (linear_applier) {
139 linear_applier(currentIndex,
140 LinearPathComponent(points_[component.index],
141 points_[component.index + 1]));
142 }
143 break;
144 case ComponentType::kQuadratic:
145 if (quad_applier) {
146 quad_applier(currentIndex,
147 QuadraticPathComponent(points_[component.index],
148 points_[component.index + 1],
149 points_[component.index + 2]));
150 }
151 break;
152 case ComponentType::kCubic:
153 if (cubic_applier) {
154 cubic_applier(currentIndex,
155 CubicPathComponent(points_[component.index],
156 points_[component.index + 1],
157 points_[component.index + 2],
158 points_[component.index + 3]));
159 }
160 break;
161 case ComponentType::kContour:
162 if (contour_applier) {
163 contour_applier(currentIndex, contours_[component.index]);
164 }
165 break;
166 }
167 currentIndex++;
168 }
169 }
170
171 bool Path::GetLinearComponentAtIndex(size_t index,
172 LinearPathComponent& linear) const {
173 if (index >= components_.size()) {
174 return false;
175 }
176
177 if (components_[index].type != ComponentType::kLinear) {
178 return false;
179 }
180
181 auto point_index = components_[index].index;
182 linear = LinearPathComponent(points_[point_index], points_[point_index + 1]);
183 return true;
184 }
185
186 bool Path::GetQuadraticComponentAtIndex(
187 size_t index,
188 QuadraticPathComponent& quadratic) const {
189 if (index >= components_.size()) {
190 return false;
191 }
192
193 if (components_[index].type != ComponentType::kQuadratic) {
194 return false;
195 }
196
197 auto point_index = components_[index].index;
198 quadratic = QuadraticPathComponent(
199 points_[point_index], points_[point_index + 1], points_[point_index + 2]);
200 return true;
201 }
202
203 bool Path::GetCubicComponentAtIndex(size_t index,
204 CubicPathComponent& cubic) const {
205 if (index >= components_.size()) {
206 return false;
207 }
208
209 if (components_[index].type != ComponentType::kCubic) {
210 return false;
211 }
212
213 auto point_index = components_[index].index;
214 cubic =
215 CubicPathComponent(points_[point_index], points_[point_index + 1],
216 points_[point_index + 2], points_[point_index + 3]);
217 return true;
218 }
219
220 bool Path::GetContourComponentAtIndex(size_t index,
221 ContourComponent& move) const {
222 if (index >= components_.size()) {
223 return false;
224 }
225
226 if (components_[index].type != ComponentType::kContour) {
227 return false;
228 }
229
230 move = contours_[components_[index].index];
231 return true;
232 }
233
234 Path::Polyline::Polyline(Path::Polyline::PointBufferPtr point_buffer,
235 Path::Polyline::ReclaimPointBufferCallback reclaim)
236 : points(std::move(point_buffer)), reclaim_points_(std::move(reclaim)) {
237 FML_DCHECK(points);
238 }
239
240 Path::Polyline::Polyline(Path::Polyline&& other) {
241 points = std::move(other.points);
242 reclaim_points_ = std::move(other.reclaim_points_);
243 contours = std::move(other.contours);
244 }
245
246 Path::Polyline::~Polyline() {
247 if (reclaim_points_) {
248 points->clear();
249 reclaim_points_(std::move(points));
250 }
251 }
252
253 Path::Polyline Path::CreatePolyline(
254 Scalar scale,
255 Path::Polyline::PointBufferPtr point_buffer,
256 Path::Polyline::ReclaimPointBufferCallback reclaim) const {
257 Polyline polyline(std::move(point_buffer), std::move(reclaim));
258
259 auto get_path_component = [this](size_t component_i) -> PathComponentVariant {
260 if (component_i >= components_.size()) {
261 return std::monostate{};
262 }
263 const auto& component = components_[component_i];
264 switch (component.type) {
265 case ComponentType::kLinear:
266 return reinterpret_cast<const LinearPathComponent*>(
267 &points_[component.index]);
268 case ComponentType::kQuadratic:
269 return reinterpret_cast<const QuadraticPathComponent*>(
270 &points_[component.index]);
271 case ComponentType::kCubic:
272 return reinterpret_cast<const CubicPathComponent*>(
273 &points_[component.index]);
274 case ComponentType::kContour:
275 return std::monostate{};
276 }
277 };
278
279 auto compute_contour_start_direction =
280 [&get_path_component](size_t current_path_component_index) {
281 size_t next_component_index = current_path_component_index + 1;
282 while (!std::holds_alternative<std::monostate>(
283 get_path_component(next_component_index))) {
284 auto next_component = get_path_component(next_component_index);
285 auto maybe_vector =
286 std::visit(PathComponentStartDirectionVisitor(), next_component);
287 if (maybe_vector.has_value()) {
288 return maybe_vector.value();
289 } else {
290 next_component_index++;
291 }
292 }
293 return Vector2(0, -1);
294 };
295
296 std::vector<PolylineContour::Component> components;
297 std::optional<size_t> previous_path_component_index;
298 auto end_contour = [&polyline, &previous_path_component_index,
299 &get_path_component, &components]() {
300 // Whenever a contour has ended, extract the exact end direction from
301 // the last component.
302 if (polyline.contours.empty()) {
303 return;
304 }
305
306 if (!previous_path_component_index.has_value()) {
307 return;
308 }
309
310 auto& contour = polyline.contours.back();
311 contour.end_direction = Vector2(0, 1);
312 contour.components = components;
313 components.clear();
314
315 size_t previous_index = previous_path_component_index.value();
316 while (!std::holds_alternative<std::monostate>(
317 get_path_component(previous_index))) {
318 auto previous_component = get_path_component(previous_index);
319 auto maybe_vector =
320 std::visit(PathComponentEndDirectionVisitor(), previous_component);
321 if (maybe_vector.has_value()) {
322 contour.end_direction = maybe_vector.value();
323 break;
324 } else {
325 if (previous_index == 0) {
326 break;
327 }
328 previous_index--;
329 }
330 }
331 };
332
333 for (size_t component_i = 0; component_i < components_.size();
334 component_i++) {
335 const auto& component = components_[component_i];
336 switch (component.type) {
337 case ComponentType::kLinear:
338 components.push_back({
339 .component_start_index = polyline.points->size() - 1,
340 .is_curve = false,
341 });
342 reinterpret_cast<const LinearPathComponent*>(&points_[component.index])
343 ->AppendPolylinePoints(*polyline.points);
344 previous_path_component_index = component_i;
345 break;
346 case ComponentType::kQuadratic:
347 components.push_back({
348 .component_start_index = polyline.points->size() - 1,
349 .is_curve = true,
350 });
351 reinterpret_cast<const QuadraticPathComponent*>(
352 &points_[component.index])
353 ->AppendPolylinePoints(scale, *polyline.points);
354 previous_path_component_index = component_i;
355 break;
356 case ComponentType::kCubic:
357 components.push_back({
358 .component_start_index = polyline.points->size() - 1,
359 .is_curve = true,
360 });
361 reinterpret_cast<const CubicPathComponent*>(&points_[component.index])
362 ->AppendPolylinePoints(scale, *polyline.points);
363 previous_path_component_index = component_i;
364 break;
365 case ComponentType::kContour:
366 if (component_i == components_.size() - 1) {
367 // If the last component is a contour, that means it's an empty
368 // contour, so skip it.
369 continue;
370 }
371 end_contour();
372
373 Vector2 start_direction = compute_contour_start_direction(component_i);
374 const auto& contour = contours_[component.index];
375 polyline.contours.push_back({.start_index = polyline.points->size(),
376 .is_closed = contour.is_closed,
377 .start_direction = start_direction,
378 .components = components});
379
380 polyline.points->push_back(contour.destination);
381 break;
382 }
383 }
384 end_contour();
385 return polyline;
386 }
387
388 std::optional<Rect> Path::GetBoundingBox() const {
389 return computed_bounds_;
390 }
391
392 void Path::ComputeBounds() {
393 auto min_max = GetMinMaxCoveragePoints();
394 if (!min_max.has_value()) {
395 computed_bounds_ = std::nullopt;
396 return;
397 }
398 auto min = min_max->first;
399 auto max = min_max->second;
400 const auto difference = max - min;
401 computed_bounds_ = Rect::MakeXYWH(min.x, min.y, difference.x, difference.y);
402 }
403
404 std::optional<Rect> Path::GetTransformedBoundingBox(
405 const Matrix& transform) const {
406 auto bounds = GetBoundingBox();
407 if (!bounds.has_value()) {
408 return std::nullopt;
409 }
410 return bounds->TransformBounds(transform);
411 }
412
413 std::optional<std::pair<Point, Point>> Path::GetMinMaxCoveragePoints() const {
414 if (points_.empty()) {
415 return std::nullopt;
416 }
417
418 std::optional<Point> min, max;
419
420 auto clamp = [&min, &max](const Point& point) {
421 if (min.has_value()) {
422 min = min->Min(point);
423 } else {
424 min = point;
425 }
426
427 if (max.has_value()) {
428 max = max->Max(point);
429 } else {
430 max = point;
431 }
432 };
433
434 for (const auto& component : components_) {
435 switch (component.type) {
436 case ComponentType::kLinear: {
437 auto* linear = reinterpret_cast<const LinearPathComponent*>(
438 &points_[component.index]);
439 clamp(linear->p1);
440 clamp(linear->p2);
441 break;
442 }
443 case ComponentType::kQuadratic:
444 for (const auto& extrema :
445 reinterpret_cast<const QuadraticPathComponent*>(
446 &points_[component.index])
447 ->Extrema()) {
448 clamp(extrema);
449 }
450 break;
451 case ComponentType::kCubic:
452 for (const auto& extrema : reinterpret_cast<const CubicPathComponent*>(
453 &points_[component.index])
454 ->Extrema()) {
455 clamp(extrema);
456 }
457 break;
458 case ComponentType::kContour:
459 break;
460 }
461 }
462
463 if (!min.has_value() || !max.has_value()) {
464 return std::nullopt;
465 }
466
467 return std::make_pair(min.value(), max.value());
468 }
469
470 void Path::SetBounds(Rect rect) {
471 computed_bounds_ = rect;
472 }
473
474 } // namespace impeller
path.h
point.h
impeller::LinearPathComponent
Definition: path_component.h:28
impeller::Path::GetQuadraticComponentAtIndex
bool GetQuadraticComponentAtIndex(size_t index, QuadraticPathComponent &quadratic) const
Definition: path.cc:186
impeller::Scalar
float Scalar
Definition: scalar.h:18
impeller::Path::Clone
Path Clone() const
Deeply clone this path and all data associated with it.
Definition: path.cc:76
impeller::TRect< Scalar >::MakeXYWH
constexpr static TRect MakeXYWH(Type x, Type y, Type width, Type height)
Definition: rect.h:34
impeller::Path::ComponentType::kLinear
@ kLinear
impeller::Path::ComponentType::kCubic
@ kCubic
impeller::Vector2
Point Vector2
Definition: point.h:312
impeller::PathComponentEndDirectionVisitor
Definition: path_component.h:174
impeller::Path::GetBoundingBox
std::optional< Rect > GetBoundingBox() const
Definition: path.cc:388
impeller::Convexity
Convexity
Definition: path.h:38
impeller::Path::GetMinMaxCoveragePoints
std::optional< std::pair< Point, Point > > GetMinMaxCoveragePoints() const
Definition: path.cc:413
impeller::Path::ComponentType::kQuadratic
@ kQuadratic
impeller::Path::Polyline::Polyline
Polyline(PointBufferPtr point_buffer, ReclaimPointBufferCallback reclaim)
Definition: path.cc:234
impeller::Path::~Path
~Path()
impeller::Path::Polyline
Definition: path.h:98
impeller::Point
TPoint< Scalar > Point
Definition: point.h:308
impeller::Path::GetCubicComponentAtIndex
bool GetCubicComponentAtIndex(size_t index, CubicPathComponent &cubic) const
Definition: path.cc:203
impeller::Path::Polyline::ReclaimPointBufferCallback
std::function< void(PointBufferPtr)> ReclaimPointBufferCallback
Definition: path.h:102
impeller::Path::EnumerateComponents
void EnumerateComponents(const Applier< LinearPathComponent > &linear_applier, const Applier< QuadraticPathComponent > &quad_applier, const Applier< CubicPathComponent > &cubic_applier, const Applier< ContourComponent > &contour_applier) const
Definition: path.cc:129
impeller::Path
Paths are lightweight objects that describe a collection of linear, quadratic, or cubic segments....
Definition: path.h:55
impeller::Path::GetTransformedBoundingBox
std::optional< Rect > GetTransformedBoundingBox(const Matrix &transform) const
Definition: path.cc:404
impeller::Path::Polyline::GetContourPointBounds
std::tuple< size_t, size_t > GetContourPointBounds(size_t contour_index) const
Definition: path.cc:22
impeller::FillType
FillType
Definition: path.h:30
impeller::PathComponentStartDirectionVisitor
Definition: path_component.h:165
impeller::QuadraticPathComponent::Extrema
std::vector< Point > Extrema() const
Definition: path_component.cc:149
impeller::Path::Path
Path()
Definition: path.cc:16
impeller::Path::GetContourComponentAtIndex
bool GetContourComponentAtIndex(size_t index, ContourComponent &contour) const
Definition: path.cc:220
impeller::CubicPathComponent
Definition: path_component.h:94
impeller::Path::Polyline::points
PointBufferPtr points
Definition: path.h:113
impeller::Path::Applier
std::function< void(size_t index, const T &component)> Applier
Definition: path.h:148
std
Definition: comparable.h:95
impeller::TPoint
Definition: point.h:22
impeller::Path::GetComponentCount
size_t GetComponentCount(std::optional< ComponentType > type={}) const
Definition: path.cc:34
impeller::Path::Polyline::contours
std::vector< PolylineContour > contours
Definition: path.h:119
impeller::Path::Polyline::PointBufferPtr
std::unique_ptr< std::vector< Point > > PointBufferPtr
Definition: path.h:101
impeller::Path::Polyline::~Polyline
~Polyline()
Definition: path.cc:246
impeller::Path::IsConvex
bool IsConvex() const
Definition: path.cc:59
impeller::CubicPathComponent::Extrema
std::vector< Point > Extrema() const
Definition: path_component.cc:308
impeller::PathComponentVariant
std::variant< std::monostate, const LinearPathComponent *, const QuadraticPathComponent *, const CubicPathComponent * > PathComponentVariant
Definition: path_component.h:163
path_component.h
impeller::ContourComponent
Definition: path_component.h:146
impeller::Convexity::kConvex
@ kConvex
impeller
Definition: aiks_context.cc:10
impeller::QuadraticPathComponent
Definition: path_component.h:52
impeller::Path::CreatePolyline
Polyline CreatePolyline(Scalar scale, Polyline::PointBufferPtr point_buffer=std::make_unique< std::vector< Point >>(), Polyline::ReclaimPointBufferCallback reclaim=nullptr) const
Definition: path.cc:253
impeller::Path::GetLinearComponentAtIndex
bool GetLinearComponentAtIndex(size_t index, LinearPathComponent &linear) const
Definition: path.cc:171
impeller::Path::ComponentType::kContour
@ kContour
impeller::TRect
Definition: rect.h:22
impeller::Matrix
A 4x4 matrix using column-major storage.
Definition: matrix.h:37
impeller::Path::GetFillType
FillType GetFillType() const
Definition: path.cc:55