Flutter Impeller
path_builder.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 "path_builder.h"
6 
7 #include <cmath>
8 
10 
11 namespace impeller {
12 
14  AddContourComponent({});
15 }
16 
17 PathBuilder::~PathBuilder() = default;
18 
20  prototype_.fill = fill;
21  prototype_.single_countour =
22  current_contour_location_ == 0u ||
23  (contour_count_ == 2 &&
24  prototype_.components.back() == Path::ComponentType::kContour);
25  return Path(prototype_);
26 }
27 
29  prototype_.fill = fill;
30  UpdateBounds();
31  prototype_.single_countour =
32  current_contour_location_ == 0u ||
33  (contour_count_ == 2 &&
34  prototype_.components.back() == Path::ComponentType::kContour);
35  current_contour_location_ = 0u;
36  contour_count_ = 1;
37  return Path(std::move(prototype_));
38 }
39 
40 void PathBuilder::Reserve(size_t point_size, size_t verb_size) {
41  prototype_.points.reserve(point_size);
42  prototype_.components.reserve(verb_size);
43 }
44 
45 PathBuilder& PathBuilder::MoveTo(Point point, bool relative) {
46  current_ = relative ? current_ + point : point;
47  subpath_start_ = current_;
48  AddContourComponent(current_);
49  return *this;
50 }
51 
53  // If the subpath start is the same as the current position, this
54  // is an empty contour and inserting a line segment will just
55  // confuse the tessellator.
56  if (subpath_start_ != current_) {
57  LineTo(subpath_start_);
58  }
59  SetContourClosed(true);
60  AddContourComponent(current_);
61  return *this;
62 }
63 
64 PathBuilder& PathBuilder::LineTo(Point point, bool relative) {
65  point = relative ? current_ + point : point;
66  AddLinearComponent(current_, point);
67  current_ = point;
68  return *this;
69 }
70 
72  Point endpoint =
73  relative ? Point{current_.x + x, current_.y} : Point{x, current_.y};
74  AddLinearComponent(current_, endpoint);
75  current_ = endpoint;
76  return *this;
77 }
78 
80  Point endpoint =
81  relative ? Point{current_.x, current_.y + y} : Point{current_.x, y};
82  AddLinearComponent(current_, endpoint);
83  current_ = endpoint;
84  return *this;
85 }
86 
88  Point point,
89  bool relative) {
90  point = relative ? current_ + point : point;
91  controlPoint = relative ? current_ + controlPoint : controlPoint;
92  AddQuadraticComponent(current_, controlPoint, point);
93  current_ = point;
94  return *this;
95 }
96 
98  prototype_.convexity = value;
99  return *this;
100 }
101 
103  Point controlPoint2,
104  Point point,
105  bool relative) {
106  controlPoint1 = relative ? current_ + controlPoint1 : controlPoint1;
107  controlPoint2 = relative ? current_ + controlPoint2 : controlPoint2;
108  point = relative ? current_ + point : point;
109  AddCubicComponent(current_, controlPoint1, controlPoint2, point);
110  current_ = point;
111  return *this;
112 }
113 
115  MoveTo(p1);
116  AddQuadraticComponent(p1, cp, p2);
117  return *this;
118 }
119 
121  Point cp1,
122  Point cp2,
123  Point p2) {
124  MoveTo(p1);
125  AddCubicComponent(p1, cp1, cp2, p2);
126  return *this;
127 }
128 
130  auto origin = rect.GetOrigin();
131  auto size = rect.GetSize();
132 
133  auto tl = origin;
134  auto bl = origin + Point{0.0, size.height};
135  auto br = origin + size;
136  auto tr = origin + Point{size.width, 0.0};
137 
138  MoveTo(tl);
139  LineTo(tr);
140  LineTo(br);
141  LineTo(bl);
142  Close();
143 
144  return *this;
145 }
146 
148  return AddOval(Rect::MakeXYWH(c.x - r, c.y - r, 2.0f * r, 2.0f * r));
149 }
150 
152  auto rect = round_rect.GetBounds();
153  auto radii = round_rect.GetRadii();
154  if (radii.AreAllCornersEmpty()) {
155  return AddRect(rect);
156  }
157 
158  auto rect_origin = rect.GetOrigin();
159  auto rect_size = rect.GetSize();
160 
161  current_ = rect_origin + Point{radii.top_left.width, 0.0};
162 
163  MoveTo({rect_origin.x + radii.top_left.width, rect_origin.y});
164 
165  //----------------------------------------------------------------------------
166  // Top line.
167  //
168  AddLinearComponentIfNeeded(
169  {rect_origin.x + radii.top_left.width, rect_origin.y},
170  {rect_origin.x + rect_size.width - radii.top_right.width, rect_origin.y});
171 
172  //----------------------------------------------------------------------------
173  // Top right arc.
174  //
175  AddRoundedRectTopRight(rect, radii);
176 
177  //----------------------------------------------------------------------------
178  // Right line.
179  //
180  AddLinearComponentIfNeeded(
181  {rect_origin.x + rect_size.width, rect_origin.y + radii.top_right.height},
182  {rect_origin.x + rect_size.width,
183  rect_origin.y + rect_size.height - radii.bottom_right.height});
184 
185  //----------------------------------------------------------------------------
186  // Bottom right arc.
187  //
188  AddRoundedRectBottomRight(rect, radii);
189 
190  //----------------------------------------------------------------------------
191  // Bottom line.
192  //
193  AddLinearComponentIfNeeded(
194  {rect_origin.x + rect_size.width - radii.bottom_right.width,
195  rect_origin.y + rect_size.height},
196  {rect_origin.x + radii.bottom_left.width,
197  rect_origin.y + rect_size.height});
198 
199  //----------------------------------------------------------------------------
200  // Bottom left arc.
201  //
202  AddRoundedRectBottomLeft(rect, radii);
203 
204  //----------------------------------------------------------------------------
205  // Left line.
206  //
207  AddLinearComponentIfNeeded(
208  {rect_origin.x,
209  rect_origin.y + rect_size.height - radii.bottom_left.height},
210  {rect_origin.x, rect_origin.y + radii.top_left.height});
211 
212  //----------------------------------------------------------------------------
213  // Top left arc.
214  //
215  AddRoundedRectTopLeft(rect, radii);
216 
217  Close();
218 
219  return *this;
220 }
221 
222 PathBuilder& PathBuilder::AddRoundedRectTopLeft(Rect rect,
223  RoundingRadii radii) {
224  const auto magic_top_left = radii.top_left * kArcApproximationMagic;
225  const auto corner = rect.GetOrigin();
226  AddCubicComponent(
227  {corner.x, corner.y + radii.top_left.height},
228  {corner.x, corner.y + radii.top_left.height - magic_top_left.height},
229  {corner.x + radii.top_left.width - magic_top_left.width, corner.y},
230  {corner.x + radii.top_left.width, corner.y});
231  return *this;
232 }
233 
234 PathBuilder& PathBuilder::AddRoundedRectTopRight(Rect rect,
235  RoundingRadii radii) {
236  const auto magic_top_right = radii.top_right * kArcApproximationMagic;
237  const auto corner = rect.GetOrigin() + Point{rect.GetWidth(), 0};
238  AddCubicComponent(
239  {corner.x - radii.top_right.width, corner.y},
240  {corner.x - radii.top_right.width + magic_top_right.width, corner.y},
241  {corner.x, corner.y + radii.top_right.height - magic_top_right.height},
242  {corner.x, corner.y + radii.top_right.height});
243  return *this;
244 }
245 
246 PathBuilder& PathBuilder::AddRoundedRectBottomRight(Rect rect,
247  RoundingRadii radii) {
248  const auto magic_bottom_right = radii.bottom_right * kArcApproximationMagic;
249  const auto corner = rect.GetOrigin() + rect.GetSize();
250  AddCubicComponent(
251  {corner.x, corner.y - radii.bottom_right.height},
252  {corner.x,
253  corner.y - radii.bottom_right.height + magic_bottom_right.height},
254  {corner.x - radii.bottom_right.width + magic_bottom_right.width,
255  corner.y},
256  {corner.x - radii.bottom_right.width, corner.y});
257  return *this;
258 }
259 
260 PathBuilder& PathBuilder::AddRoundedRectBottomLeft(Rect rect,
261  RoundingRadii radii) {
262  const auto magic_bottom_left = radii.bottom_left * kArcApproximationMagic;
263  const auto corner = rect.GetOrigin() + Point{0, rect.GetHeight()};
264  AddCubicComponent(
265  {corner.x + radii.bottom_left.width, corner.y},
266  {corner.x + radii.bottom_left.width - magic_bottom_left.width, corner.y},
267  {corner.x,
268  corner.y - radii.bottom_left.height + magic_bottom_left.height},
269  {corner.x, corner.y - radii.bottom_left.height});
270  return *this;
271 }
272 
273 void PathBuilder::AddContourComponent(const Point& destination,
274  bool is_closed) {
275  auto& components = prototype_.components;
276  auto& points = prototype_.points;
277  auto closed = is_closed ? Point{0, 0} : Point{1, 1};
278  if (components.size() > 0 &&
279  components.back() == Path::ComponentType::kContour) {
280  // Never insert contiguous contours.
281  points[current_contour_location_] = destination;
282  points[current_contour_location_ + 1] = closed;
283  } else {
284  current_contour_location_ = points.size();
285  points.push_back(destination);
286  points.push_back(closed);
287  components.push_back(Path::ComponentType::kContour);
288  contour_count_ += 1;
289  }
290  prototype_.bounds.reset();
291 }
292 
293 void PathBuilder::AddLinearComponentIfNeeded(const Point& p1, const Point& p2) {
294  if (ScalarNearlyEqual(p1.x, p2.x, 1e-4f) &&
295  ScalarNearlyEqual(p1.y, p2.y, 1e-4f)) {
296  return;
297  }
298  AddLinearComponent(p1, p2);
299 }
300 
301 void PathBuilder::AddLinearComponent(const Point& p1, const Point& p2) {
302  auto& points = prototype_.points;
303  points.push_back(p1);
304  points.push_back(p2);
305  prototype_.components.push_back(Path::ComponentType::kLinear);
306  prototype_.bounds.reset();
307 }
308 
309 void PathBuilder::AddQuadraticComponent(const Point& p1,
310  const Point& cp,
311  const Point& p2) {
312  auto& points = prototype_.points;
313  points.push_back(p1);
314  points.push_back(cp);
315  points.push_back(p2);
316  prototype_.components.push_back(Path::ComponentType::kQuadratic);
317  prototype_.bounds.reset();
318 }
319 
320 void PathBuilder::AddCubicComponent(const Point& p1,
321  const Point& cp1,
322  const Point& cp2,
323  const Point& p2) {
324  auto& points = prototype_.points;
325  points.push_back(p1);
326  points.push_back(cp1);
327  points.push_back(cp2);
328  points.push_back(p2);
329  prototype_.components.push_back(Path::ComponentType::kCubic);
330  prototype_.bounds.reset();
331 }
332 
333 void PathBuilder::SetContourClosed(bool is_closed) {
334  prototype_.points[current_contour_location_ + 1] =
335  is_closed ? Point{0, 0} : Point{1, 1};
336 }
337 
339  Radians start,
340  Radians sweep,
341  bool use_center) {
342  if (sweep.radians < 0) {
343  start.radians += sweep.radians;
344  sweep.radians *= -1;
345  }
346  sweep.radians = std::min(k2Pi, sweep.radians);
347  start.radians = std::fmod(start.radians, k2Pi);
348 
349  const Point center = oval_bounds.GetCenter();
350  const Point radius = center - oval_bounds.GetOrigin();
351 
352  Vector2 p1_unit(std::cos(start.radians), std::sin(start.radians));
353 
354  if (use_center) {
355  MoveTo(center);
356  LineTo(center + p1_unit * radius);
357  } else {
358  MoveTo(center + p1_unit * radius);
359  }
360 
361  while (sweep.radians > 0) {
362  Vector2 p2_unit;
363  Scalar quadrant_angle;
364  if (sweep.radians < kPiOver2) {
365  quadrant_angle = sweep.radians;
366  p2_unit = Vector2(std::cos(start.radians + quadrant_angle),
367  std::sin(start.radians + quadrant_angle));
368  } else {
369  quadrant_angle = kPiOver2;
370  p2_unit = Vector2(-p1_unit.y, p1_unit.x);
371  }
372 
373  Vector2 arc_cp_lengths =
374  (quadrant_angle / kPiOver2) * kArcApproximationMagic * radius;
375 
376  Point p1 = center + p1_unit * radius;
377  Point p2 = center + p2_unit * radius;
378  Point cp1 = p1 + Vector2(-p1_unit.y, p1_unit.x) * arc_cp_lengths;
379  Point cp2 = p2 + Vector2(p2_unit.y, -p2_unit.x) * arc_cp_lengths;
380 
381  AddCubicComponent(p1, cp1, cp2, p2);
382  current_ = p2;
383 
384  start.radians += quadrant_angle;
385  sweep.radians -= quadrant_angle;
386  p1_unit = p2_unit;
387  }
388 
389  if (use_center) {
390  Close();
391  }
392 
393  return *this;
394 }
395 
397  const Point c = container.GetCenter();
398  const Point r = c - container.GetOrigin();
399  const Point m = r * kArcApproximationMagic;
400 
401  MoveTo({c.x, c.y - r.y});
402 
403  //----------------------------------------------------------------------------
404  // Top right arc.
405  //
406  AddCubicComponent({c.x, c.y - r.y}, // p1
407  {c.x + m.x, c.y - r.y}, // cp1
408  {c.x + r.x, c.y - m.y}, // cp2
409  {c.x + r.x, c.y} // p2
410  );
411 
412  //----------------------------------------------------------------------------
413  // Bottom right arc.
414  //
415  AddCubicComponent({c.x + r.x, c.y}, // p1
416  {c.x + r.x, c.y + m.y}, // cp1
417  {c.x + m.x, c.y + r.y}, // cp2
418  {c.x, c.y + r.y} // p2
419  );
420 
421  //----------------------------------------------------------------------------
422  // Bottom left arc.
423  //
424  AddCubicComponent({c.x, c.y + r.y}, // p1
425  {c.x - m.x, c.y + r.y}, // cp1
426  {c.x - r.x, c.y + m.y}, // cp2
427  {c.x - r.x, c.y} // p2
428  );
429 
430  //----------------------------------------------------------------------------
431  // Top left arc.
432  //
433  AddCubicComponent({c.x - r.x, c.y}, // p1
434  {c.x - r.x, c.y - m.y}, // cp1
435  {c.x - m.x, c.y - r.y}, // cp2
436  {c.x, c.y - r.y} // p2
437  );
438 
439  Close();
440 
441  return *this;
442 }
443 
444 PathBuilder& PathBuilder::AddLine(const Point& p1, const Point& p2) {
445  MoveTo(p1);
446  AddLinearComponent(p1, p2);
447  return *this;
448 }
449 
451  auto& points = prototype_.points;
452  auto& components = prototype_.components;
453 
454  points.insert(points.end(), path.data_->points.begin(),
455  path.data_->points.end());
456  components.insert(components.end(), path.data_->components.begin(),
457  path.data_->components.end());
458 
459  size_t source_offset = points.size();
460  for (auto component : path.data_->components) {
461  if (component == Path::ComponentType::kContour) {
462  current_contour_location_ = source_offset;
463  contour_count_ += 1;
464  }
465  source_offset += Path::VerbToOffset(component);
466  }
467  return *this;
468 }
469 
471  auto& points = prototype_.points;
472  size_t storage_offset = 0u;
473  for (const auto& component : prototype_.components) {
474  switch (component) {
476  auto* linear =
477  reinterpret_cast<LinearPathComponent*>(&points[storage_offset]);
478  linear->p1 += offset;
479  linear->p2 += offset;
480  break;
481  }
483  auto* quad =
484  reinterpret_cast<QuadraticPathComponent*>(&points[storage_offset]);
485  quad->p1 += offset;
486  quad->p2 += offset;
487  quad->cp += offset;
488  } break;
490  auto* cubic =
491  reinterpret_cast<CubicPathComponent*>(&points[storage_offset]);
492  cubic->p1 += offset;
493  cubic->p2 += offset;
494  cubic->cp1 += offset;
495  cubic->cp2 += offset;
496  } break;
498  auto* contour =
499  reinterpret_cast<ContourComponent*>(&points[storage_offset]);
500  contour->destination += offset;
501  break;
502  }
503  storage_offset += Path::VerbToOffset(component);
504  }
505 
506  prototype_.bounds.reset();
507  return *this;
508 }
509 
511  prototype_.bounds = bounds;
512  return *this;
513 }
514 
515 void PathBuilder::UpdateBounds() {
516  if (!prototype_.bounds.has_value()) {
517  auto min_max = GetMinMaxCoveragePoints();
518  if (!min_max.has_value()) {
519  prototype_.bounds.reset();
520  return;
521  }
522  auto min = min_max->first;
523  auto max = min_max->second;
524  const auto difference = max - min;
525  prototype_.bounds =
526  Rect::MakeXYWH(min.x, min.y, difference.x, difference.y);
527  }
528 }
529 
530 std::optional<std::pair<Point, Point>> PathBuilder::GetMinMaxCoveragePoints()
531  const {
532  auto& points = prototype_.points;
533 
534  if (points.empty()) {
535  return std::nullopt;
536  }
537 
538  std::optional<Point> min, max;
539 
540  auto clamp = [&min, &max](const Point& point) {
541  if (min.has_value()) {
542  min = min->Min(point);
543  } else {
544  min = point;
545  }
546 
547  if (max.has_value()) {
548  max = max->Max(point);
549  } else {
550  max = point;
551  }
552  };
553 
554  size_t storage_offset = 0u;
555  for (const auto& component : prototype_.components) {
556  switch (component) {
558  auto* linear = reinterpret_cast<const LinearPathComponent*>(
559  &points[storage_offset]);
560  clamp(linear->p1);
561  clamp(linear->p2);
562  break;
563  }
565  for (const auto& extrema :
566  reinterpret_cast<const QuadraticPathComponent*>(
567  &points[storage_offset])
568  ->Extrema()) {
569  clamp(extrema);
570  }
571  break;
573  for (const auto& extrema : reinterpret_cast<const CubicPathComponent*>(
574  &points[storage_offset])
575  ->Extrema()) {
576  clamp(extrema);
577  }
578  break;
580  break;
581  }
582  storage_offset += Path::VerbToOffset(component);
583  }
584 
585  if (!min.has_value() || !max.has_value()) {
586  return std::nullopt;
587  }
588 
589  return std::make_pair(min.value(), max.value());
590 }
591 
592 } // namespace impeller
PathBuilder & AddRect(Rect rect)
Path TakePath(FillType fill=FillType::kNonZero)
Definition: path_builder.cc:28
PathBuilder & AddArc(const Rect &oval_bounds, Radians start, Radians sweep, bool use_center=false)
PathBuilder & AddRoundRect(RoundRect rect)
PathBuilder & LineTo(Point point, bool relative=false)
Insert a line from the current position to point.
Definition: path_builder.cc:64
PathBuilder & MoveTo(Point point, bool relative=false)
Definition: path_builder.cc:45
PathBuilder & SetBounds(Rect bounds)
Set the bounding box that will be used by Path.GetBoundingBox in place of performing the computation.
void Reserve(size_t point_size, size_t verb_size)
Reserve [point_size] points and [verb_size] verbs in the underlying path buffer.
Definition: path_builder.cc:40
PathBuilder & AddOval(const Rect &rect)
PathBuilder & AddCircle(const Point &center, Scalar radius)
PathBuilder & Close()
Definition: path_builder.cc:52
PathBuilder & AddPath(const Path &path)
Path CopyPath(FillType fill=FillType::kNonZero)
Definition: path_builder.cc:19
PathBuilder & VerticalLineTo(Scalar y, bool relative=false)
Definition: path_builder.cc:79
PathBuilder & Shift(Point offset)
Transform the existing path segments and contours by the given offset.
PathBuilder & AddLine(const Point &p1, const Point &p2)
Move to point p1, then insert a line from p1 to p2.
constexpr static const Scalar kArcApproximationMagic
Definition: path_builder.h:24
PathBuilder & AddQuadraticCurve(Point p1, Point cp, Point p2)
Move to point p1, then insert a quadradic curve from p1 to p2 with the control point cp.
PathBuilder & CubicCurveTo(Point controlPoint1, Point controlPoint2, Point point, bool relative=false)
Insert a cubic curve from the curren position to point using the control points controlPoint1 and con...
PathBuilder & AddCubicCurve(Point p1, Point cp1, Point cp2, Point p2)
Move to point p1, then insert a cubic curve from p1 to p2 with control points cp1 and cp2.
PathBuilder & HorizontalLineTo(Scalar x, bool relative=false)
Definition: path_builder.cc:71
PathBuilder & SetConvexity(Convexity value)
Definition: path_builder.cc:97
PathBuilder & QuadraticCurveTo(Point controlPoint, Point point, bool relative=false)
Insert a quadradic curve from the current position to point using the control point controlPoint.
Definition: path_builder.cc:87
Paths are lightweight objects that describe a collection of linear, quadratic, or cubic segments....
Definition: path.h:53
static constexpr size_t VerbToOffset(Path::ComponentType verb)
Definition: path.h:62
int32_t value
int32_t x
constexpr float k2Pi
Definition: constants.h:29
Point Vector2
Definition: point.h:331
float Scalar
Definition: scalar.h:18
FillType
Definition: path.h:31
TRect< Scalar > Rect
Definition: rect.h:792
TPoint< Scalar > Point
Definition: point.h:327
Convexity
Definition: path.h:36
constexpr float kPiOver2
Definition: constants.h:32
constexpr bool ScalarNearlyEqual(Scalar x, Scalar y, Scalar tolerance=kEhCloseEnough)
Definition: scalar.h:35
SeparatedVector2 offset
Scalar radians
Definition: scalar.h:44
constexpr const RoundingRadii & GetRadii() const
Definition: round_rect.h:44
constexpr const Rect & GetBounds() const
Definition: round_rect.h:43
constexpr TPoint< Type > GetOrigin() const
Returns the upper left corner of the rectangle as specified by the left/top or x/y values when it was...
Definition: rect.h:324
constexpr TSize< Type > GetSize() const
Returns the size of the rectangle which may be negative in either width or height and may have been c...
Definition: rect.h:331
constexpr static TRect MakeXYWH(Type x, Type y, Type width, Type height)
Definition: rect.h:136
constexpr Point GetCenter() const
Get the center point as a |Point|.
Definition: rect.h:386
Type height
Definition: size.h:29
Type width
Definition: size.h:28