Flutter Impeller
stroke_path_geometry.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 
6 
7 #include "flutter/display_list/geometry/dl_path.h"
17 
18 namespace impeller {
19 
20 namespace {
21 
22 class PositionWriter {
23  public:
24  explicit PositionWriter(std::vector<Point>& points)
25  : points_(points), oversized_() {
26  FML_DCHECK(points_.size() == kPointArenaSize);
27  }
28 
29  void AppendVertex(const Point& point) {
30  if (offset_ >= kPointArenaSize) {
31  oversized_.push_back(point);
32  } else {
33  points_[offset_++] = point;
34  }
35  }
36 
37  /// @brief Return the number of points used in the arena, followed by
38  /// the number of points allocated in the overized buffer.
39  std::pair<size_t, size_t> GetUsedSize() const {
40  return std::make_pair(offset_, oversized_.size());
41  }
42 
43  bool HasOversizedBuffer() const { return !oversized_.empty(); }
44 
45  const std::vector<Point>& GetOversizedBuffer() const { return oversized_; }
46 
47  private:
48  std::vector<Point>& points_;
49  std::vector<Point> oversized_;
50  size_t offset_ = 0u;
51 };
52 
53 } // namespace
54 
55 /// StrokePathSegmentReceiver converts path segments (fed by PathTessellator)
56 /// into a vertex strip that covers the outline of the stroked version of the
57 /// path and feeds those vertices, expressed in the form of a vertex strip
58 /// into the supplied PositionWriter.
59 ///
60 /// The general procedure follows the following basic methodology:
61 ///
62 /// Every path segment is represented by a box with two starting vertices
63 /// perpendicular to its start point and two vertices perpendicular to its
64 /// end point, all perpendiculars of length (stroke_width * 0.5).
65 ///
66 /// Joins will connect the ending "box" perpendiculars of the previous segment
67 /// to the starting "box" perpendiculars of the following segment. If the two
68 /// boxes are so aligned that their adjacent perpendiculars are less than a
69 /// threshold distance apart (kJoinPixelThreshold), the join will just be
70 /// elided so that the end of one box becomes the start of the next box.
71 /// If the join process does add decorations, it assumes that the ending
72 /// perpendicular vertices from the prior segment are the last vertices
73 /// added and ensures that it appends the two vertices for the starting
74 /// perpendiculars of the new segment's "box". Thus every join either
75 /// adds nothing and the end perpendiculars of the previous segment become
76 /// the start perpendiculars of the next segment, or it makes sure its
77 /// geometry fills in the gap and ends with the start perpendiculars for the
78 /// new segment.
79 ///
80 /// Prior to the start of an unclosed contour we insert a cap and also the
81 /// starting perpendicular segments for the first segment. Prior to the
82 /// start of a closed contour, we just insert the starting perpendiculars
83 /// for the first segment. Either way, we've initialized the path with the
84 /// starting perpendiculars of the first segment.
85 ///
86 /// After the last segment in an unclosed contour we insert a cap which
87 /// can assume that the last segment has already inserted its closing
88 /// perpendicular segments. After the last segment in a closed contour, we
89 /// insert a join back to the very first segment in that contour.
90 ///
91 /// Connecting any two contours we insert an infinitely thin connecting
92 /// thread by inserting the last point of the previous contour twice and
93 /// then inserting the first point of the next contour twice. This ensures
94 /// that there are no non-empty triangles between the two contours.
95 ///
96 /// Finally, inserting a line segment can assume that the starting
97 /// perpendiculars have already been inserted by the preceding cap, join,
98 /// or prior segment, so all it needs to do is to insert the ending
99 /// perpendiculars which set the process up for the subsequent cap, join,
100 /// or future segment.
101 ///
102 /// Inserting curve segments acts like a series of line segments except
103 /// that the opening perpendicular is taken from the curve rather than the
104 /// direction between the starting point and the first sample point. This
105 /// ensures that any cap or join will be aligned with the curve and not
106 /// tilted by the first approximating segment. The same is true of the
107 /// ending perpendicular which is taken from the curve and not the last
108 /// approximated segment. Between each approximated segment of the curve,
109 /// we insert only Cap::kRound joins so as not to polygonize a curve when
110 /// it turns very sharply. We also skip these joins for any change of
111 /// direction which is smaller than the first sample point of a round join
112 /// for performance reasons.
113 ///
114 /// To facilitate all of that work we maintain variables containing
115 /// SeparatedVector2 values that, by convention, point 90 degrees to the
116 /// right of the given path direction. This facilitates a quick add/subtract
117 /// from the point on the path to insert the necessary perpendicular
118 /// points of a segment's box. These values contain both a unit vector for
119 /// direction and a magnitude for length.
120 ///
121 /// SeparatedVector2 values also allow us to quickly test limits on when to
122 /// include joins by using a simple dot product on the previous and next
123 /// perpendiculars at a given path point which should match the dot product
124 /// of the path's direction itself at the same point since both perpendiculars
125 /// have been rotated identically to the same side of the path.
126 /// The SeparatedVector2 will perform the dot product on the unit-length
127 /// vectors so that the result is exactly the cosine of the angle between the
128 /// segments - also the angle by which the path turned at a given path point.
129 ///
130 /// @see PathTessellator::PathToStrokedSegments
132  public:
134  PositionWriter& vtx_builder,
135  const StrokeParameters& stroke,
136  const Scalar scale)
137  : tessellator_(tessellator),
138  vtx_builder_(vtx_builder),
139  half_stroke_width_(stroke.width * 0.5f),
140  maximum_join_cosine_(
141  ComputeMaximumJoinCosine(scale, half_stroke_width_)),
142  minimum_miter_cosine_(ComputeMinimumMiterCosine(stroke.miter_limit)),
143  join_(stroke.join),
144  cap_(stroke.cap),
145  scale_(scale),
146  trigs_(MakeTrigs(tessellator, scale, half_stroke_width_)) {
147  // Trigs ensures that it always contains at least 2 entries.
148  FML_DCHECK(trigs_.size() >= 2);
149  FML_DCHECK(trigs_[0].cos == 1.0f); // Angle == 0 degrees
150  FML_DCHECK(trigs_[0].sin == 0.0f);
151  FML_DCHECK(trigs_.end()[-1].cos == 0.0f); // Angle == 90 degrees
152  FML_DCHECK(trigs_.end()[-1].sin == 1.0f);
153  }
154 
155  protected:
156  // |SegmentReceiver|
157  void BeginContour(Point origin, bool will_be_closed) override {
158  if (has_prior_contour_ && origin != last_point_) {
159  // We only append these extra points if we have had a prior contour.
160  vtx_builder_.AppendVertex(last_point_);
161  vtx_builder_.AppendVertex(last_point_);
162  vtx_builder_.AppendVertex(origin);
163  vtx_builder_.AppendVertex(origin);
164  }
165  has_prior_contour_ = true;
166  has_prior_segment_ = false;
167  contour_needs_cap_ = !will_be_closed;
168  last_point_ = origin;
169  origin_point_ = origin;
170  }
171 
172  // |SegmentReceiver|
173  void RecordLine(Point p1, Point p2) override {
174  if (p2 != p1) {
175  SeparatedVector2 current_perpendicular = PerpendicularFromPoints(p1, p2);
176 
177  HandlePreviousJoin(current_perpendicular);
178  AppendVertices(p2, current_perpendicular);
179 
180  last_perpendicular_ = current_perpendicular;
181  last_point_ = p2;
182  }
183  }
184 
185  // |SegmentReceiver|
186  void RecordQuad(Point p1, Point cp, Point p2) override {
187  RecordCurve<PathTessellator::Quad>({p1, cp, p2});
188  }
189 
190  // |SegmentReceiver|
191  void RecordConic(Point p1, Point cp, Point p2, Scalar weight) override {
192  RecordCurve<PathTessellator::Conic>({p1, cp, p2, weight});
193  }
194 
195  // |SegmentReceiver|
196  void RecordCubic(Point p1, Point cp1, Point cp2, Point p2) override {
197  RecordCurve<PathTessellator::Cubic>({p1, cp1, cp2, p2});
198  }
199 
200  // Utility implementation of |SegmentReceiver| Record<Curve> methods
201  template <typename Curve>
202  inline void RecordCurve(const Curve& curve) {
203  std::optional<Point> start_direction = curve.GetStartDirection();
204  std::optional<Point> end_direction = curve.GetEndDirection();
205 
206  // The Prune receiver should have eliminated any empty curves, so any
207  // curve we see should have both start and end direction.
208  FML_DCHECK(start_direction.has_value() && end_direction.has_value());
209 
210  // In order to keep the compiler/lint happy we check for values anyway.
211  if (start_direction.has_value() && end_direction.has_value()) {
212  // We now know the curve cannot be degenerate.
213  SeparatedVector2 start_perpendicular =
214  PerpendicularFromUnitDirection(-start_direction.value());
215  SeparatedVector2 end_perpendicular =
216  PerpendicularFromUnitDirection(end_direction.value());
217 
218  // We join the previous segment to this one with a normal join
219  // The join will append the perpendicular at the start of this
220  // curve as well.
221  HandlePreviousJoin(start_perpendicular);
222 
223  // We use the scale suggested by the transform basis which is the
224  // same scale that would be used for filling the path. But we also
225  // need to adjust the scale for the magnification of the curve
226  // features that occurs when we draw with a wide pen and the outer
227  // curves of that stroked path are larger than the base curve itself.
228  // So, we scale by both the transform basis and (half) the stroke
229  // width, but we also make sure that we aren't reducing the scale
230  // in the uncommon case that someone is drawing at a large scale
231  // with a very tiny stroke width. To accomplish this, we multiply
232  // the scale basis by half the stroke width, but make sure the width
233  // is at least 1.0 so that we don't reduce the natural transform scale.
234  Scalar stroke_scale = scale_ * std::max(1.0f, half_stroke_width_);
235  Scalar count = std::ceilf(curve.SubdivisionCount(stroke_scale));
236 
237  Point prev = curve.p1;
238  SeparatedVector2 prev_perpendicular = start_perpendicular;
239 
240  // Handle all intermediate curve points up to but not including the end.
241  for (int i = 1; i < count; i++) {
242  Point cur = curve.Solve(i / count);
243  SeparatedVector2 cur_perpendicular = PerpendicularFromPoints(prev, cur);
244  RecordCurveSegment(prev_perpendicular, cur, cur_perpendicular);
245  prev = cur;
246  prev_perpendicular = cur_perpendicular;
247  }
248 
249  RecordCurveSegment(prev_perpendicular, curve.p2, end_perpendicular);
250 
251  last_perpendicular_ = end_perpendicular;
252  last_point_ = curve.p2;
253  }
254  }
255 
256  void RecordCurveSegment(const SeparatedVector2& prev_perpendicular,
257  const Point cur,
258  const SeparatedVector2& cur_perpendicular) {
259  if (prev_perpendicular.GetAlignment(cur_perpendicular) < trigs_[1].cos) {
260  // We only connect 2 curved segments if their change in direction
261  // is faster than a single sample of a round join. We always use a
262  // round join here because this is about smoothness of curves rather
263  // than a decoration for specific segments of the path.
264  AppendVertices(cur, prev_perpendicular);
265  AddJoin(Join::kRound, cur, prev_perpendicular, cur_perpendicular);
266  }
267  AppendVertices(cur, cur_perpendicular);
268  }
269 
270  // |SegmentReceiver|
271  void EndContour(Point origin, bool with_close) override {
272  FML_DCHECK(origin == origin_point_);
273  if (!has_prior_segment_) {
274  // Empty contour, fill in an axis aligned "cap box" at the origin.
275  FML_DCHECK(last_point_ == origin);
276  // kButt wouldn't fill anything so it defers to kSquare by convention.
277  Cap cap = (cap_ == Cap::kButt) ? Cap::kSquare : cap_;
278  Vector2 perpendicular = {-half_stroke_width_, 0};
279  AddCap(cap, origin, perpendicular, true);
280  if (cap == Cap::kRound) {
281  // Only round caps need the perpendicular between them to connect.
282  AppendVertices(origin, perpendicular);
283  }
284  AddCap(cap, origin, perpendicular, false);
285  } else if (with_close) {
286  // Closed contour, join back to origin.
287  FML_DCHECK(origin == origin_point_);
288  FML_DCHECK(last_point_ == origin);
289  AddJoin(join_, origin, last_perpendicular_, origin_perpendicular_);
290 
291  last_perpendicular_ = origin_perpendicular_;
292  last_point_ = origin;
293  } else {
294  AddCap(cap_, last_point_, last_perpendicular_.GetVector(), false);
295  }
296  has_prior_segment_ = false;
297  }
298 
299  // |PathAndArcSegmentReceiver|
300  void RecordArc(const Arc& arc,
301  const Point center,
302  const Size radii) override {
303  Tessellator::Trigs trigs =
304  tessellator_.GetTrigsForDeviceRadius(scale_ * radii.MaxDimension());
305  Arc::Iteration iterator = arc.ComputeIterations(trigs.GetSteps(), false);
306 
307  SeparatedVector2 prev_perpendicular =
308  PerpendicularFromUnitDirection({-iterator.start.y, iterator.start.x});
309  HandlePreviousJoin(prev_perpendicular);
310 
311  for (size_t i = 0u; i < iterator.quadrant_count; i++) {
312  Arc::Iteration::Quadrant quadrant = iterator.quadrants[i];
313  for (size_t j = quadrant.start_index; j < quadrant.end_index; j++) {
314  Vector2 direction = trigs[j] * quadrant.axis;
315  Point cur = center + direction * radii;
316  SeparatedVector2 cur_perpendicular =
317  PerpendicularFromUnitDirection({-direction.y, direction.x});
318  RecordCurveSegment(prev_perpendicular, cur, cur_perpendicular);
319  prev_perpendicular = cur_perpendicular;
320  }
321  }
322 
323  SeparatedVector2 end_perpendicular =
324  PerpendicularFromUnitDirection({-iterator.end.y, iterator.end.x});
325  Point end = center + iterator.end * radii;
326  RecordCurveSegment(prev_perpendicular, end, end_perpendicular);
327 
328  last_perpendicular_ = end_perpendicular;
329  last_point_ = end;
330  }
331 
332  private:
333  Tessellator& tessellator_;
334  PositionWriter& vtx_builder_;
335  const Scalar half_stroke_width_;
336  const Scalar maximum_join_cosine_;
337  const Scalar minimum_miter_cosine_;
338  const Join join_;
339  const Cap cap_;
340  const Scalar scale_;
341  const Tessellator::Trigs trigs_;
342 
343  SeparatedVector2 origin_perpendicular_;
344  Point origin_point_;
345  SeparatedVector2 last_perpendicular_;
346  Point last_point_;
347  bool has_prior_contour_ = false;
348  bool has_prior_segment_ = false;
349  bool contour_needs_cap_ = false;
350 
351  static Tessellator::Trigs MakeTrigs(Tessellator& tessellator,
352  Scalar scale,
353  Scalar half_stroke_width) {
354  return tessellator.GetTrigsForDeviceRadius(scale * half_stroke_width);
355  }
356 
357  // Half of the allowed distance between the ends of the perpendiculars.
358  static constexpr Scalar kJoinPixelThreshold = 0.25f;
359 
360  /// Determine the cosine of the angle where the ends of 2 vectors that are
361  /// each as long as half of the stroke width, differ by less than the
362  /// kJoinPixelThreshold.
363  ///
364  /// Any angle between 2 segments in the path for which the cosine of that
365  /// angle is greater than this return value, do not need any kind of join
366  /// geometry. The angle between the segments can be quickly computed by
367  /// the dot product of their direction vectors.
368  static Scalar ComputeMaximumJoinCosine(Scalar scale,
369  Scalar half_stroke_width) {
370  // Consider 2 perpendicular vectors, each pointing to the same side of
371  // two adjacent path segment "boxes". If they are identical, then there
372  // is no turn at that point on the path and we do not need to decorate
373  // that gap with any join geometry. If they differ, there will be a gap
374  // between them that must be decorated and the cosine of the angle of
375  // that gap will be their Dot product (with +1 meaning that there is
376  // no turn and therefore no decoration needed). We need to find the
377  // cosine of the angle between them where we start to care about adding
378  // the join geometry.
379  //
380  // Consider the right triangle where one side is the line bisecting the
381  // two perpendiculars, starting from the common point on the path and
382  // ending at the line that joins them. The hypotenuse of that triangle
383  // is one of the perpendiculars, whose length is (scale * half_width).
384  // The other non-hypotenuse side is kJoinPixelThreshold. This
385  // triangle establishes the equation:
386  // ||bisector|| ^ 2 + kJoinThreshold ^ 2 == ||hypotenuse|| ^ 2
387  // and the cosine of the angle between the perpendicular and the bisector
388  // will be (||bisector|| / ||hypotenuse||).
389  // The cosine between the perpendiculars which can be compared to the
390  // will be the cosine of double that angle.
391  Scalar hypotenuse = scale * half_stroke_width;
392  if (hypotenuse <= kJoinPixelThreshold) {
393  // The line geometry is too small to register the docorations. Return
394  // a cosine value small enough to never qualify to add join decorations.
395  return -1.1f;
396  }
397  Scalar bisector = std::sqrt(hypotenuse * hypotenuse -
398  kJoinPixelThreshold * kJoinPixelThreshold);
399  Scalar half_cosine = bisector / hypotenuse;
400  Scalar cosine = 2.0f * half_cosine * half_cosine - 1;
401  return cosine;
402  }
403 
404  /// Determine the cosine of the angle between 2 segments on the path where
405  /// the miter limit will be exceeded if their outer stroked outlines are
406  /// joined at their intersection. The miter limit is expressed as a multiple
407  /// of the stroke width and since it is dependent on lines offset from the
408  /// path by that same stroke width, the angle is based just on the miter
409  /// limit itself.
410  ///
411  /// Any angle between 2 segments in the path for which the cosine of that
412  /// angle is less than this return value would result in an intersection
413  /// point that is further than the miter limit would allow. The angle
414  /// between the segments can be quickly computed by the dot product of
415  /// their direction vectors.
416  static Scalar ComputeMinimumMiterCosine(Scalar miter_limit) {
417  if (miter_limit <= 1.0f) {
418  // Miter limits less than 1.0 are impossible to meet since the miter
419  // join will always be at least as long as half the line width, so they
420  // essentially eliminate all miters. We return a degenerate cosine
421  // value so that the join routine never adds a miter.
422  return 1.1f;
423  }
424  // We enter the join routine with a point on the path shared between
425  // two segments that must be joined and 2 perpendicular values that
426  // locate the sides of the old and new segment "boxes" relative to
427  // that point. We can think of the miter as a diamond starting at the
428  // point on the path, extending outwards by those 2 perpendicular
429  // lines, and then continuing perpendicular to those perpendiculars
430  // to a common intersection point out in the distance. If you then
431  // consider the line that extends from the path point to the far
432  // intersection point, that divides the diamond into 2 right triangles
433  // (they are right triangles due to the right angle turn we take at
434  // the ends of the path perpendiculars). If we want to know the angle
435  // at which we reach the miter limit we can assume maximum extension
436  // which places the dividing line (the hypotenuse) at a multiple of the
437  // line width which is the length of one of those segment perpendiculars.
438  // This means that the near bisected angle has a cosine of the ratio
439  // of one of the near edges (length of half the line width) with the
440  // miter length (miter_limit times half the line width). The ratio of
441  // those is (1 / miter_limit).
442  Scalar half_cosine = 1 / miter_limit;
443  Scalar cosine = 2.0f * half_cosine * half_cosine - 1;
444  return cosine;
445  }
446 
447  inline SeparatedVector2 PerpendicularFromPoints(const Point from,
448  const Point to) const {
449  return PerpendicularFromUnitDirection((to - from).Normalize());
450  }
451 
452  inline SeparatedVector2 PerpendicularFromUnitDirection(
453  const Vector2 direction) const {
454  return SeparatedVector2(Vector2{-direction.y, direction.x},
455  half_stroke_width_);
456  }
457 
458  inline void AppendVertices(const Point curve_point, Vector2 offset) {
459  vtx_builder_.AppendVertex(curve_point + offset);
460  vtx_builder_.AppendVertex(curve_point - offset);
461  }
462 
463  inline void AppendVertices(const Point curve_point,
464  SeparatedVector2 perpendicular) {
465  return AppendVertices(curve_point, perpendicular.GetVector());
466  }
467 
468  inline void HandlePreviousJoin(SeparatedVector2 new_perpendicular) {
469  FML_DCHECK(has_prior_contour_);
470  if (has_prior_segment_) {
471  AddJoin(join_, last_point_, last_perpendicular_, new_perpendicular);
472  } else {
473  has_prior_segment_ = true;
474  Vector2 perpendicular_vector = new_perpendicular.GetVector();
475  if (contour_needs_cap_) {
476  AddCap(cap_, last_point_, perpendicular_vector, true);
477  }
478  // Start the new segment's "box" at the shared "last_point_" with
479  // the new perpendicular vector.
480  AppendVertices(last_point_, perpendicular_vector);
481  origin_perpendicular_ = new_perpendicular;
482  }
483  }
484 
485  // Adds a cap to an endpoint of a contour. The location points to the
486  // centerline of the stroke. The perpendicular points clockwise to the
487  // direction the path is traveling and is the length of half of the
488  // stroke width.
489  //
490  // If contour_start is true, then the cap is being added prior to the first
491  // segment at the beginning of a contour and assumes that no points have
492  // been added for this contour yet and also that the caller will add the
493  // two points that start the segment's "box" when this method returns.
494  //
495  // If contour_start is false, then the cap is being added after the last
496  // segment at the end of a contour and assumes that the caller has already
497  // added the two segments that define the end of the "box" for the last
498  // path segment.
499  void AddCap(Cap cap,
500  Point path_point,
501  Vector2 perpendicular,
502  bool contour_start) {
503  switch (cap) {
504  case Cap::kButt:
505  break;
506  case Cap::kRound: {
507  Point along(perpendicular.y, -perpendicular.x);
508  if (contour_start) {
509  // Start with a single point at the far end of the round cap.
510  vtx_builder_.AppendVertex(path_point - along);
511 
512  // Iterate from the last non-quadrant value in the trigs vector
513  // (trigs.back() == (1, 0)) down to, but not including, the first
514  // entry (which is (0, 1)).
515  for (size_t i = trigs_.size() - 2u; i > 0u; --i) {
516  Point center = path_point - along * trigs_[i].sin;
517  Vector2 offset = perpendicular * trigs_[i].cos;
518 
519  AppendVertices(center, offset);
520  }
521  } else {
522  // Iterate from the first non-quadrant value in the trigs vector
523  // (trigs[0] == (0, 1)) up to, but not including, the last entry
524  // (which is (0, 1)).
525  size_t end = trigs_.size() - 1u;
526  for (size_t i = 1u; i < end; ++i) {
527  Point center = path_point + along * trigs_[i].sin;
528  Vector2 offset = perpendicular * trigs_[i].cos;
529 
530  AppendVertices(center, offset);
531  }
532 
533  // End with a single point at the far end of the round cap.
534  vtx_builder_.AppendVertex(path_point + along);
535  }
536  break;
537  }
538  case Cap::kSquare: {
539  Point along(perpendicular.y, -perpendicular.x);
540  Point square_center = contour_start //
541  ? path_point - along //
542  : path_point + along;
543  AppendVertices(square_center, perpendicular);
544  break;
545  }
546  }
547  }
548 
549  void AddJoin(Join join,
550  Point path_point,
551  SeparatedVector2 old_perpendicular,
552  SeparatedVector2 new_perpendicular) {
553  Scalar cosine = old_perpendicular.GetAlignment(new_perpendicular);
554  if (cosine >= maximum_join_cosine_) {
555  // If the perpendiculars are closer than a pixel to each other, then
556  // no need to add any further points, we don't even need to start
557  // the new segment's "box", we can just let it connect back to the
558  // prior segment's "box end" directly.
559  return;
560  }
561  // All cases of this switch will fall through into the code that starts
562  // the new segment's "box" down below which is good enough to bevel join
563  // the segments should they individually decide that they don't need any
564  // other decorations to bridge the gap.
565  switch (join) {
566  case Join::kBevel:
567  // Just fall through to the bevel operation after the switch.
568  break;
569 
570  case Join::kMiter: {
571  if (cosine >= minimum_miter_cosine_) {
572  Point miter_vector =
573  (old_perpendicular.GetVector() + new_perpendicular.GetVector()) /
574  (cosine + 1);
575  if (old_perpendicular.Cross(new_perpendicular) < 0) {
576  vtx_builder_.AppendVertex(path_point + miter_vector);
577  } else {
578  vtx_builder_.AppendVertex(path_point - miter_vector);
579  }
580  }
581  // Else just fall through to bevel operation after the switch.
582  break;
583  } // end of case Join::kMiter
584 
585  case Join::kRound: {
586  if (cosine >= trigs_[1].cos) {
587  // If rotating by the first (non-quadrant) entry in trigs takes
588  // us too far then we don't need to fill in anything. Just fall
589  // through to the bevel operation after the switch.
590  break;
591  }
592  if (cosine < -trigs_[1].cos) {
593  // This is closer to a 180 degree turn than the last trigs entry
594  // can distinguish. Since we are going to generate all of the
595  // sample points of the entire round join anyway, it is faster to
596  // generate them using a round cap operation. Additionally, it
597  // avoids math issues in the code below that stem from the
598  // calculations being performed on a pair of vectors that are
599  // nearly opposite each other.
600  AddCap(Cap::kRound, path_point, old_perpendicular.GetVector(), false);
601  // The bevel operation following the switch statement will set
602  // us up to start drawing the following segment.
603  break;
604  }
605  // We want to set up the from and to vectors to facilitate a
606  // clockwise angular fill from one to the other. We might generate
607  // a couple fewer points by iterating counter-clockwise in some
608  // cases so that we always go from the old to new perpendiculars,
609  // but there is a lot of code to duplicate below for just a small
610  // change in whether we negate the trigs and expect the a.Cross(b)
611  // values to be > or < 0.
612  Vector2 from_vector, to_vector;
613  bool begin_end_crossed;
614  Scalar turning = old_perpendicular.Cross(new_perpendicular);
615  if (turning > 0) {
616  // Clockwise path turn, since our prependicular vectors point to
617  // the right of the path we need to fill in the "back side" of the
618  // turn, so we fill from -old to -new perpendicular which also
619  // has a clockwise turn.
620  from_vector = -old_perpendicular.GetVector();
621  to_vector = -new_perpendicular.GetVector();
622  // Despite the fact that we are using the negative vectors, they
623  // are in the right "order" so we can connect directly from the
624  // prior segment's "box" and directly to the following segment's.
625  begin_end_crossed = false;
626  } else {
627  // Countercockwise path turn, we need to reverse the order of the
628  // perpendiculars to achieve a clockwise angular fill, and since
629  // both vectors are pointing to the right, the vectors themselves
630  // are "turning outside" the widened path.
631  from_vector = new_perpendicular.GetVector();
632  to_vector = old_perpendicular.GetVector();
633  // We are reversing the direction of traversal with respect to
634  // the old segment's and new segment's boxes so we should append
635  // extra segments to cross back and forth.
636  begin_end_crossed = true;
637  }
638  FML_DCHECK(from_vector.Cross(to_vector) > 0);
639 
640  if (begin_end_crossed) {
641  vtx_builder_.AppendVertex(path_point + from_vector);
642  }
643 
644  // We only need to trace back to the common center point on every
645  // other circular vertex we add. This generates a "corrugated"
646  // path that visits the center once for every pair of edge vertices.
647  bool visit_center = false;
648 
649  // The sum of the vectors points in the direction halfway between
650  // them. Since we only need its direction, this is enough without
651  // having to adjust for the length to get the exact midpoint of
652  // the curve we have to draw.
653  Point middle_vector = (from_vector + to_vector);
654 
655  // Iterate through trigs until we reach a full quadrant's rotation
656  // or until we pass the halfway point (middle_vector). We start at
657  // position 1 because the first value is (0, 1) and just repeats
658  // the from_vector, and we choose the end here as the last value
659  // rather than the end of the vector because it is (1, 0) and that
660  // would just repeat the to_vector. The end variable will be updated
661  // in the first loop if we stop short of a full quadrant.
662  size_t end = trigs_.size() - 1u;
663  for (size_t i = 1u; i < end; ++i) {
664  Point p = trigs_[i] * from_vector;
665  if (p.Cross(middle_vector) <= 0) {
666  // We've traversed far enough to pass the halfway vector, stop
667  // here and drop out to traverse backwards from the to_vector.
668  // Record the stopping point in the end variable as we will use
669  // it to backtrack in the next loop.
670  end = i;
671  break;
672  }
673  if (visit_center) {
674  vtx_builder_.AppendVertex(path_point);
675  visit_center = false;
676  } else {
677  visit_center = true;
678  }
679  vtx_builder_.AppendVertex(path_point + p);
680  }
681 
682  // The end variable points to the last trigs entry we decided not to
683  // use, so a pre-decrement here moves us onto the trigs we actually
684  // want to use (stopping before we use 0 which is the no rotation
685  // vector).
686  while (--end > 0u) {
687  Point p = -trigs_[end] * to_vector;
688  if (visit_center) {
689  vtx_builder_.AppendVertex(path_point);
690  visit_center = false;
691  } else {
692  visit_center = true;
693  }
694  vtx_builder_.AppendVertex(path_point + p);
695  }
696 
697  if (begin_end_crossed) {
698  vtx_builder_.AppendVertex(path_point + to_vector);
699  }
700  break;
701  } // end of case Join::kRound
702  } // end of switch
703  // All joins need a final segment that is perpendicular to the shared
704  // path point along the new perpendicular direction, and this also
705  // provides a bevel join for all cases that decided no further
706  // decoration was warranted.
707  AppendVertices(path_point, new_perpendicular);
708  }
709 };
710 
711 // Private for benchmarking and debugging
712 std::vector<Point> StrokeSegmentsGeometry::GenerateSolidStrokeVertices(
713  Tessellator& tessellator,
714  const PathSource& source,
715  const StrokeParameters& stroke,
716  Scalar scale) {
717  std::vector<Point> points(4096);
718  PositionWriter vtx_builder(points);
719  StrokePathSegmentReceiver receiver(tessellator, vtx_builder, stroke, scale);
720  PathTessellator::PathToStrokedSegments(source, receiver);
721  auto [arena, extra] = vtx_builder.GetUsedSize();
722  FML_DCHECK(extra == 0u);
723  points.resize(arena);
724  return points;
725 }
726 
728  : stroke_(stroke) {}
729 
731 
733  return stroke_.width;
734 }
735 
737  return stroke_.miter_limit;
738 }
739 
741  return stroke_.cap;
742 }
743 
745  return stroke_.join;
746 }
747 
749  const Matrix& transform) const {
751 }
752 
753 GeometryResult StrokeSegmentsGeometry::GetPositionBuffer(
754  const ContentContext& renderer,
755  const Entity& entity,
756  RenderPass& pass) const {
757  if (stroke_.width < 0.0) {
758  return {};
759  }
760  Scalar max_basis = entity.GetTransform().GetMaxBasisLengthXY();
761  if (max_basis == 0) {
762  return {};
763  }
764 
765  Scalar min_size = kMinStrokeSize / max_basis;
766  StrokeParameters adjusted_stroke = stroke_;
767  adjusted_stroke.width = std::max(stroke_.width, min_size);
768 
769  auto& data_host_buffer = renderer.GetTransientsDataBuffer();
770  auto scale = entity.GetTransform().GetMaxBasisLengthXY();
771  auto& tessellator = renderer.GetTessellator();
772 
773  PositionWriter position_writer(tessellator.GetStrokePointCache());
774  StrokePathSegmentReceiver receiver(tessellator, position_writer,
775  adjusted_stroke, scale);
776  Dispatch(receiver, tessellator, scale);
777 
778  const auto [arena_length, oversized_length] = position_writer.GetUsedSize();
779  if (!position_writer.HasOversizedBuffer()) {
780  BufferView buffer_view =
781  data_host_buffer.Emplace(tessellator.GetStrokePointCache().data(),
782  arena_length * sizeof(Point), alignof(Point));
783 
784  return GeometryResult{.type = PrimitiveType::kTriangleStrip,
785  .vertex_buffer =
786  {
787  .vertex_buffer = buffer_view,
788  .vertex_count = arena_length,
789  .index_type = IndexType::kNone,
790  },
791  .transform = entity.GetShaderTransform(pass),
793  }
794  const std::vector<Point>& oversized_data =
795  position_writer.GetOversizedBuffer();
796  BufferView buffer_view = data_host_buffer.Emplace(
797  /*buffer=*/nullptr, //
798  (arena_length + oversized_length) * sizeof(Point), //
799  alignof(Point) //
800  );
801  memcpy(buffer_view.GetBuffer()->OnGetContents() +
802  buffer_view.GetRange().offset, //
803  tessellator.GetStrokePointCache().data(), //
804  arena_length * sizeof(Point) //
805  );
806  memcpy(buffer_view.GetBuffer()->OnGetContents() +
807  buffer_view.GetRange().offset + arena_length * sizeof(Point), //
808  oversized_data.data(), //
809  oversized_data.size() * sizeof(Point) //
810  );
811  buffer_view.GetBuffer()->Flush(buffer_view.GetRange());
812 
813  return GeometryResult{.type = PrimitiveType::kTriangleStrip,
814  .vertex_buffer =
815  {
816  .vertex_buffer = buffer_view,
817  .vertex_count = arena_length + oversized_length,
818  .index_type = IndexType::kNone,
819  },
820  .transform = entity.GetShaderTransform(pass),
822 }
823 
824 GeometryResult::Mode StrokeSegmentsGeometry::GetResultMode() const {
826 }
827 
829  const Matrix& transform,
830  const Rect& path_bounds) const {
831  if (path_bounds.IsEmpty()) {
832  return std::nullopt;
833  }
834 
835  Scalar max_radius = 0.5;
836  if (stroke_.cap == Cap::kSquare) {
837  max_radius = max_radius * kSqrt2;
838  }
839  if (stroke_.join == Join::kMiter) {
840  max_radius = std::max(max_radius, stroke_.miter_limit * 0.5f);
841  }
842  Scalar max_basis = transform.GetMaxBasisLengthXY();
843  if (max_basis == 0) {
844  return {};
845  }
846  // Use the most conervative coverage setting.
847  Scalar min_size = kMinStrokeSize / max_basis;
848  max_radius *= std::max(stroke_.width, min_size);
849  return path_bounds.Expand(max_radius).TransformBounds(transform);
850 }
851 
853  const StrokeParameters& parameters)
854  : StrokeSegmentsGeometry(parameters) {}
855 
857  const Matrix& transform) const {
858  return GetStrokeCoverage(transform, GetSource().GetBounds());
859 }
860 
862  Tessellator& tessellator,
863  Scalar scale) const {
865 }
866 
868  const StrokeParameters& parameters)
869  : StrokePathSourceGeometry(parameters), path_(path) {}
870 
872  return path_;
873 }
874 
876  const StrokeParameters& parameters)
877  : StrokeSegmentsGeometry(parameters), arc_(arc) {}
878 
879 std::optional<Rect> ArcStrokeGeometry::GetCoverage(
880  const Matrix& transform) const {
882 }
883 
885  Tessellator& tessellator,
886  Scalar scale) const {
887  Point center = arc_.GetOvalCenter();
888  Size radii = arc_.GetOvalSize() * 0.5f;
889 
890  auto trigs =
891  tessellator.GetTrigsForDeviceRadius(scale * radii.MaxDimension());
892  Arc::Iteration iterator =
893  arc_.ComputeIterations(trigs.GetSteps(), /*simplify_360=*/false);
894  Point start = center + iterator.start * radii;
895  bool include_center = arc_.IncludeCenter();
896 
897  receiver.BeginContour(start, include_center);
898  receiver.RecordArc(arc_, center, radii);
899  if (include_center) {
900  Point end = center + iterator.end * radii;
901  receiver.RecordLine(end, center);
902  receiver.RecordLine(center, start);
903  }
904  receiver.EndContour(start, include_center);
905 }
906 
908  const RoundRect& outer,
909  const RoundRect& inner,
910  const StrokeParameters& parameters)
911  : StrokePathSourceGeometry(parameters), source_(outer, inner) {}
912 
914  return source_;
915 }
916 
918  Point p0,
919  Point p1,
920  Scalar on_length,
921  Scalar off_length,
922  const StrokeParameters& parameters)
923  : StrokePathSourceGeometry(parameters),
924  source_(p0, p1, on_length, off_length) {}
925 
927  return source_;
928 }
929 
930 } // namespace impeller
BufferView buffer_view
void Dispatch(PathAndArcSegmentReceiver &receiver, Tessellator &tessellator, Scalar scale) const override
ArcStrokeGeometry(const Arc &arc, const StrokeParameters &parameters)
std::optional< Rect > GetCoverage(const Matrix &transform) const override
HostBuffer & GetTransientsDataBuffer() const
Retrieve the current host buffer for transient storage of other non-index data.
Tessellator & GetTessellator() const
Matrix GetShaderTransform(const RenderPass &pass) const
Definition: entity.cc:50
const Matrix & GetTransform() const
Get the global transform matrix for this Entity.
Definition: entity.cc:46
static Scalar ComputeStrokeAlphaCoverage(const Matrix &entity, Scalar stroke_width)
Compute an alpha value to simulate lower coverage of fractional pixel strokes.
Definition: geometry.cc:149
A |SegmentReceiver| that also accepts Arc segments for optimal handling. A path or |PathSource| will ...
virtual void RecordArc(const Arc &arc, const Point center, const Size radii)=0
virtual void RecordLine(Point p1, Point p2)=0
virtual void EndContour(Point origin, bool with_close)=0
virtual void BeginContour(Point origin, bool will_be_closed)=0
static void PathToStrokedSegments(const PathSource &source, SegmentReceiver &receiver)
Render passes encode render commands directed as one specific render target into an underlying comman...
Definition: render_pass.h:30
const PathSource & GetSource() const override
StrokeDashedLineGeometry(Point p0, Point p1, Scalar on_length, Scalar off_length, const StrokeParameters &parameters)
StrokeDiffRoundRectGeometry(const RoundRect &outer, const RoundRect &inner, const StrokeParameters &parameters)
const PathSource & GetSource() const override
StrokePathGeometry(const flutter::DlPath &path, const StrokeParameters &parameters)
const PathSource & GetSource() const override
void BeginContour(Point origin, bool will_be_closed) override
void RecordArc(const Arc &arc, const Point center, const Size radii) override
void EndContour(Point origin, bool with_close) override
void RecordQuad(Point p1, Point cp, Point p2) override
StrokePathSegmentReceiver(Tessellator &tessellator, PositionWriter &vtx_builder, const StrokeParameters &stroke, const Scalar scale)
void RecordConic(Point p1, Point cp, Point p2, Scalar weight) override
void RecordCubic(Point p1, Point cp1, Point cp2, Point p2) override
void RecordCurveSegment(const SeparatedVector2 &prev_perpendicular, const Point cur, const SeparatedVector2 &cur_perpendicular)
void RecordLine(Point p1, Point p2) override
An abstract Geometry base class that produces fillable vertices representing the stroked outline from...
virtual const PathSource & GetSource() const =0
std::optional< Rect > GetCoverage(const Matrix &transform) const override
StrokePathSourceGeometry(const StrokeParameters &parameters)
void Dispatch(PathAndArcSegmentReceiver &receiver, Tessellator &tessellator, Scalar scale) const override
An abstract Geometry base class that produces fillable vertices representing the stroked outline of t...
virtual void Dispatch(PathAndArcSegmentReceiver &receiver, Tessellator &tessellator, Scalar scale) const =0
Scalar ComputeAlphaCoverage(const Matrix &transform) const override
std::optional< Rect > GetStrokeCoverage(const Matrix &transform, const Rect &segment_bounds) const
StrokeSegmentsGeometry(const StrokeParameters &parameters)
std::vector< Trig >::iterator end() const
Definition: tessellator.h:55
A utility that generates triangles of the specified fill type given a polyline. This happens on the C...
Definition: tessellator.h:37
Trigs GetTrigsForDeviceRadius(Scalar pixel_radius)
Definition: tessellator.cc:425
Join
An enum that describes ways to join two segments of a path.
Point Vector2
Definition: point.h:429
float Scalar
Definition: scalar.h:19
@ kNone
Does not use the index buffer.
TPoint< Scalar > Point
Definition: point.h:425
Cap
An enum that describes ways to decorate the end of a path contour.
flutter::DlPath DlPath
Definition: dl_dispatcher.h:29
constexpr float kSqrt2
Definition: constants.h:47
static constexpr size_t kPointArenaSize
The size of the point arena buffer stored on the tessellator.
Definition: tessellator.h:25
static constexpr Scalar kMinStrokeSize
Definition: geometry.h:19
Scalar weight
impeller::Vector2 axis
Definition: arc.h:46
impeller::Vector2 end
Definition: arc.h:59
impeller::Vector2 start
Definition: arc.h:58
Quadrant quadrants[9]
Definition: arc.h:86
size_t quadrant_count
Definition: arc.h:63
Iteration ComputeIterations(size_t step_count, bool simplify_360=true) const
Definition: arc.cc:102
Rect GetTightArcBounds() const
Definition: arc.cc:59
constexpr bool IncludeCenter() const
Definition: arc.h:110
const Size GetOvalSize() const
Returns the size of the oval bounds.
Definition: arc.h:100
const Point GetOvalCenter() const
Returns the center of the oval bounds.
Definition: arc.h:97
A 4x4 matrix using column-major storage.
Definition: matrix.h:37
Scalar GetMaxBasisLengthXY() const
Return the maximum scale applied specifically to either the X axis or Y axis unit vectors (the bases)...
Definition: matrix.h:328
A Vector2, broken down as a separate magnitude and direction. Assumes that the direction given is nor...
Scalar GetAlignment(const SeparatedVector2 &other) const
Vector2 GetVector() const
Returns the vector representation of the vector.
A structure to store all of the parameters related to stroking a path or basic geometry object.
constexpr TRect< T > Expand(T left, T top, T right, T bottom) const
Returns a rectangle with expanded edges. Negative expansion results in shrinking.
Definition: rect.h:618
constexpr TRect TransformBounds(const Matrix &transform) const
Creates a new bounding box that contains this transformed rectangle.
Definition: rect.h:472
constexpr bool IsEmpty() const
Returns true if either of the width or height are 0, negative, or NaN.
Definition: rect.h:297
constexpr Type MaxDimension() const
Definition: size.h:106
const size_t start
const size_t end
std::vector< Point > points