Flutter Engine Uber Docs
Docs for the entire Flutter Engine repo.
 
Loading...
Searching...
No Matches
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
17
18namespace impeller {
19
20namespace {
21
22class 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
712std::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);
721 auto [arena, extra] = vtx_builder.GetUsedSize();
722 FML_DCHECK(extra == 0u);
723 points.resize(arena);
724 return points;
725}
726
729
731
733 return stroke_.width;
734}
735
739
741 return stroke_.cap;
742}
743
745 return stroke_.join;
746}
747
752
753GeometryResult 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
824GeometryResult::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
855
857 const Matrix& transform) const {
858 return GetStrokeCoverage(transform, GetSource().GetBounds());
859}
860
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
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)
#define FML_DCHECK(condition)
Definition logging.h:122
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.
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
int32_t width
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 Type Cross(const TPoint &p) const
Definition point.h:295
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 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 Type MaxDimension() const
Definition size.h:106
const size_t start
const size_t end
std::vector< Point > points