Flutter Impeller
impeller::StrokePathSegmentReceiver Class Reference
Inheritance diagram for impeller::StrokePathSegmentReceiver:
impeller::PathAndArcSegmentReceiver impeller::PathTessellator::SegmentReceiver

Public Member Functions

 StrokePathSegmentReceiver (Tessellator &tessellator, PositionWriter &vtx_builder, const StrokeParameters &stroke, const Scalar scale)
 

Protected Member Functions

void BeginContour (Point origin, bool will_be_closed) override
 
void RecordLine (Point p1, Point p2) override
 
void RecordQuad (Point p1, Point cp, Point p2) override
 
void RecordConic (Point p1, Point cp, Point p2, Scalar weight) override
 
void RecordCubic (Point p1, Point cp1, Point cp2, Point p2) override
 
template<typename Curve >
void RecordCurve (const Curve &curve)
 
void RecordCurveSegment (const SeparatedVector2 &prev_perpendicular, const Point cur, const SeparatedVector2 &cur_perpendicular)
 
void EndContour (Point origin, bool with_close) override
 
void RecordArc (const Arc &arc, const Point center, const Size radii) override
 

Detailed Description

StrokePathSegmentReceiver converts path segments (fed by PathTessellator) into a vertex strip that covers the outline of the stroked version of the path and feeds those vertices, expressed in the form of a vertex strip into the supplied PositionWriter.

The general procedure follows the following basic methodology:

Every path segment is represented by a box with two starting vertices perpendicular to its start point and two vertices perpendicular to its end point, all perpendiculars of length (stroke_width * 0.5).

Joins will connect the ending "box" perpendiculars of the previous segment to the starting "box" perpendiculars of the following segment. If the two boxes are so aligned that their adjacent perpendiculars are less than a threshold distance apart (kJoinPixelThreshold), the join will just be elided so that the end of one box becomes the start of the next box. If the join process does add decorations, it assumes that the ending perpendicular vertices from the prior segment are the last vertices added and ensures that it appends the two vertices for the starting perpendiculars of the new segment's "box". Thus every join either adds nothing and the end perpendiculars of the previous segment become the start perpendiculars of the next segment, or it makes sure its geometry fills in the gap and ends with the start perpendiculars for the new segment.

Prior to the start of an unclosed contour we insert a cap and also the starting perpendicular segments for the first segment. Prior to the start of a closed contour, we just insert the starting perpendiculars for the first segment. Either way, we've initialized the path with the starting perpendiculars of the first segment.

After the last segment in an unclosed contour we insert a cap which can assume that the last segment has already inserted its closing perpendicular segments. After the last segment in a closed contour, we insert a join back to the very first segment in that contour.

Connecting any two contours we insert an infinitely thin connecting thread by inserting the last point of the previous contour twice and then inserting the first point of the next contour twice. This ensures that there are no non-empty triangles between the two contours.

Finally, inserting a line segment can assume that the starting perpendiculars have already been inserted by the preceding cap, join, or prior segment, so all it needs to do is to insert the ending perpendiculars which set the process up for the subsequent cap, join, or future segment.

Inserting curve segments acts like a series of line segments except that the opening perpendicular is taken from the curve rather than the direction between the starting point and the first sample point. This ensures that any cap or join will be aligned with the curve and not tilted by the first approximating segment. The same is true of the ending perpendicular which is taken from the curve and not the last approximated segment. Between each approximated segment of the curve, we insert only Cap::kRound joins so as not to polygonize a curve when it turns very sharply. We also skip these joins for any change of direction which is smaller than the first sample point of a round join for performance reasons.

To facilitate all of that work we maintain variables containing SeparatedVector2 values that, by convention, point 90 degrees to the right of the given path direction. This facilitates a quick add/subtract from the point on the path to insert the necessary perpendicular points of a segment's box. These values contain both a unit vector for direction and a magnitude for length.

SeparatedVector2 values also allow us to quickly test limits on when to include joins by using a simple dot product on the previous and next perpendiculars at a given path point which should match the dot product of the path's direction itself at the same point since both perpendiculars have been rotated identically to the same side of the path. The SeparatedVector2 will perform the dot product on the unit-length vectors so that the result is exactly the cosine of the angle between the segments - also the angle by which the path turned at a given path point.

See also
PathTessellator::PathToStrokedSegments

Definition at line 131 of file stroke_path_geometry.cc.

Constructor & Destructor Documentation

◆ StrokePathSegmentReceiver()

impeller::StrokePathSegmentReceiver::StrokePathSegmentReceiver ( Tessellator tessellator,
PositionWriter &  vtx_builder,
const StrokeParameters stroke,
const Scalar  scale 
)
inline

Definition at line 133 of file stroke_path_geometry.cc.

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  }
std::vector< Trig >::iterator end() const
Definition: tessellator.h:55

References impeller::Tessellator::Trigs::end(), and impeller::Tessellator::Trigs::size().

Member Function Documentation

◆ BeginContour()

void impeller::StrokePathSegmentReceiver::BeginContour ( Point  origin,
bool  will_be_closed 
)
inlineoverrideprotectedvirtual

Every set of path segments will be surrounded by a Begin/EndContour pair with the same origin point.

Implements impeller::PathTessellator::SegmentReceiver.

Definition at line 157 of file stroke_path_geometry.cc.

157  {
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  }

◆ EndContour()

void impeller::StrokePathSegmentReceiver::EndContour ( Point  origin,
bool  with_close 
)
inlineoverrideprotectedvirtual

Every set of path segments will be surrounded by a Begin/EndContour pair with the same origin point. The boolean indicates if the path was closed as the result of an explicit PathReceiver::Close invocation which tells a stroking sub-class whether to use end caps or a "join to first segment". Contours which are closed by a MoveTo will supply "false".

Implements impeller::PathTessellator::SegmentReceiver.

Definition at line 258 of file stroke_path_geometry.cc.

258  {
259  FML_DCHECK(origin == origin_point_);
260  if (!has_prior_segment_) {
261  // Empty contour, fill in an axis aligned "cap box" at the origin.
262  FML_DCHECK(last_point_ == origin);
263  // kButt wouldn't fill anything so it defers to kSquare by convention.
264  Cap cap = (cap_ == Cap::kButt) ? Cap::kSquare : cap_;
265  Vector2 perpendicular = {-half_stroke_width_, 0};
266  AddCap(cap, origin, perpendicular, true);
267  if (cap == Cap::kRound) {
268  // Only round caps need the perpendicular between them to connect.
269  AppendVertices(origin, perpendicular);
270  }
271  AddCap(cap, origin, perpendicular, false);
272  } else if (with_close) {
273  // Closed contour, join back to origin.
274  FML_DCHECK(origin == origin_point_);
275  FML_DCHECK(last_point_ == origin);
276  AddJoin(join_, origin, last_perpendicular_, origin_perpendicular_);
277 
278  last_perpendicular_ = origin_perpendicular_;
279  last_point_ = origin;
280  } else {
281  AddCap(cap_, last_point_, last_perpendicular_.GetVector(), false);
282  }
283  has_prior_segment_ = false;
284  }
Point Vector2
Definition: point.h:331
Cap
An enum that describes ways to decorate the end of a path contour.
Vector2 GetVector() const
Returns the vector representation of the vector.

References impeller::SeparatedVector2::GetVector(), impeller::kButt, impeller::kRound, and impeller::kSquare.

◆ RecordArc()

void impeller::StrokePathSegmentReceiver::RecordArc ( const Arc arc,
const Point  center,
const Size  radii 
)
inlineoverrideprotectedvirtual

Implements impeller::PathAndArcSegmentReceiver.

Definition at line 287 of file stroke_path_geometry.cc.

289  {
290  Tessellator::Trigs trigs =
291  tessellator_.GetTrigsForDeviceRadius(scale_ * radii.MaxDimension());
292  Arc::Iteration iterator = arc.ComputeIterations(trigs.GetSteps(), false);
293 
294  SeparatedVector2 prev_perpendicular =
295  PerpendicularFromUnitDirection({-iterator.start.y, iterator.start.x});
296  HandlePreviousJoin(prev_perpendicular);
297 
298  for (size_t i = 0u; i < iterator.quadrant_count; i++) {
299  Arc::Iteration::Quadrant quadrant = iterator.quadrants[i];
300  for (size_t j = quadrant.start_index; j < quadrant.end_index; j++) {
301  Vector2 direction = trigs[j] * quadrant.axis;
302  Point cur = center + direction * radii;
303  SeparatedVector2 cur_perpendicular =
304  PerpendicularFromUnitDirection({-direction.y, direction.x});
305  RecordCurveSegment(prev_perpendicular, cur, cur_perpendicular);
306  prev_perpendicular = cur_perpendicular;
307  }
308  }
309 
310  SeparatedVector2 end_perpendicular =
311  PerpendicularFromUnitDirection({-iterator.end.y, iterator.end.x});
312  Point end = center + iterator.end * radii;
313  RecordCurveSegment(prev_perpendicular, end, end_perpendicular);
314 
315  last_perpendicular_ = end_perpendicular;
316  last_point_ = end;
317  }
void RecordCurveSegment(const SeparatedVector2 &prev_perpendicular, const Point cur, const SeparatedVector2 &cur_perpendicular)
Trigs GetTrigsForDeviceRadius(Scalar pixel_radius)
Definition: tessellator.cc:310
TPoint< Scalar > Point
Definition: point.h:327
const size_t end

References impeller::Arc::Iteration::Quadrant::axis, impeller::Arc::ComputeIterations(), impeller::Arc::Iteration::end, end, impeller::Arc::Iteration::Quadrant::end_index, impeller::Tessellator::Trigs::GetSteps(), impeller::Tessellator::GetTrigsForDeviceRadius(), impeller::TSize< T >::MaxDimension(), impeller::Arc::Iteration::quadrant_count, impeller::Arc::Iteration::quadrants, RecordCurveSegment(), impeller::Arc::Iteration::start, impeller::Arc::Iteration::Quadrant::start_index, impeller::TPoint< T >::x, and impeller::TPoint< T >::y.

◆ RecordConic()

void impeller::StrokePathSegmentReceiver::RecordConic ( Point  p1,
Point  cp,
Point  p2,
Scalar  weight 
)
inlineoverrideprotectedvirtual

Guaranteed to be non-degenerate (not a quad or line) p1 will always be the last recorded point.

Implements impeller::PathTessellator::SegmentReceiver.

Definition at line 191 of file stroke_path_geometry.cc.

191  {
192  RecordCurve<PathTessellator::Conic>({p1, cp, p2, weight});
193  }

◆ RecordCubic()

void impeller::StrokePathSegmentReceiver::RecordCubic ( Point  p1,
Point  cp1,
Point  cp2,
Point  p2 
)
inlineoverrideprotectedvirtual

Guaranteed to be trivially non-degenerate (not all 4 points the same). p1 will always be the last recorded point.

Implements impeller::PathTessellator::SegmentReceiver.

Definition at line 196 of file stroke_path_geometry.cc.

196  {
197  RecordCurve<PathTessellator::Cubic>({p1, cp1, cp2, p2});
198  }

◆ RecordCurve()

template<typename Curve >
void impeller::StrokePathSegmentReceiver::RecordCurve ( const Curve &  curve)
inlineprotected

Definition at line 202 of file stroke_path_geometry.cc.

202  {
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  Scalar count =
224  std::ceilf(curve.SubdivisionCount(scale_ * half_stroke_width_));
225 
226  Point prev = curve.p1;
227  SeparatedVector2 prev_perpendicular = start_perpendicular;
228 
229  // Handle all intermediate curve points up to but not including the end.
230  for (int i = 1; i < count; i++) {
231  Point cur = curve.Solve(i / count);
232  SeparatedVector2 cur_perpendicular = PerpendicularFromPoints(prev, cur);
233  RecordCurveSegment(prev_perpendicular, cur, cur_perpendicular);
234  prev = cur;
235  prev_perpendicular = cur_perpendicular;
236  }
237 
238  RecordCurveSegment(prev_perpendicular, curve.p2, end_perpendicular);
239 
240  last_perpendicular_ = end_perpendicular;
241  last_point_ = curve.p2;
242  }
243  }
float Scalar
Definition: scalar.h:19

References RecordCurveSegment().

◆ RecordCurveSegment()

void impeller::StrokePathSegmentReceiver::RecordCurveSegment ( const SeparatedVector2 prev_perpendicular,
const Point  cur,
const SeparatedVector2 cur_perpendicular 
)
inlineprotected

Definition at line 245 of file stroke_path_geometry.cc.

247  {
248  if (prev_perpendicular.GetAlignment(cur_perpendicular) < trigs_[1].cos) {
249  // We only connect 2 curved segments if their change in direction
250  // is faster than a single sample of a round join.
251  AppendVertices(cur, prev_perpendicular);
252  AddJoin(Join::kRound, cur, prev_perpendicular, cur_perpendicular);
253  }
254  AppendVertices(cur, cur_perpendicular);
255  }

References impeller::SeparatedVector2::GetAlignment(), and impeller::kRound.

Referenced by RecordArc(), and RecordCurve().

◆ RecordLine()

void impeller::StrokePathSegmentReceiver::RecordLine ( Point  p1,
Point  p2 
)
inlineoverrideprotectedvirtual

Guaranteed to be non-degenerate except in the single case of stroking where we have a MoveTo followed by any number of degenerate (single point, going nowhere) path segments. p1 will always be the last recorded point.

Implements impeller::PathTessellator::SegmentReceiver.

Definition at line 173 of file stroke_path_geometry.cc.

173  {
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  }

◆ RecordQuad()

void impeller::StrokePathSegmentReceiver::RecordQuad ( Point  p1,
Point  cp,
Point  p2 
)
inlineoverrideprotectedvirtual

Guaranteed to be non-degenerate (not a line). p1 will always be the last recorded point.

Implements impeller::PathTessellator::SegmentReceiver.

Definition at line 186 of file stroke_path_geometry.cc.

186  {
187  RecordCurve<PathTessellator::Quad>({p1, cp, p2});
188  }

The documentation for this class was generated from the following file: