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