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