| Flutter Impeller |

Public Member Functions | Protected Member Functions | List of all members
impeller::StrokePathSegmentReceiver Class Reference
Inheritance diagram for impeller::StrokePathSegmentReceiver:

## 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 }
impeller::Tessellator::Trigs::end
std::vector< Trig >::iterator end() const
Definition: tessellator.h:55
impeller::Tessellator::Trigs::size
size_t size() const
Definition: tessellator.h:53

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

## Member Function Documentation

## ◆ BeginContour()

void impeller::StrokePathSegmentReceiver::BeginContour | ( | Point | origin,
|  | bool | will_be_closed
| ) |  |
| inline override protected virtual

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
| ) |  |
| inline override protected virtual

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 }
impeller::Vector2
Point Vector2
Definition: point.h:331
impeller::Cap
Cap
An enum that describes ways to decorate the end of a path contour.
Definition: stroke_parameters.h:13
impeller::Cap::kRound
@ kRound
impeller::Cap::kButt
@ kButt
impeller::Cap::kSquare
@ kSquare
impeller::SeparatedVector2::GetVector
Vector2 GetVector() const
Returns the vector representation of the vector.
Definition: separated_vector.cc:17

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
| ) |  |
| inline override protected virtual

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 }
impeller::StrokePathSegmentReceiver::RecordCurveSegment
void RecordCurveSegment(const SeparatedVector2 &prev_perpendicular, const Point cur, const SeparatedVector2 &cur_perpendicular)
Definition: stroke_path_geometry.cc:245
impeller::Tessellator::GetTrigsForDeviceRadius
Trigs GetTrigsForDeviceRadius(Scalar pixel_radius)
Definition: tessellator.cc:310
impeller::Point
TPoint< Scalar > Point
Definition: point.h:327
end
const size_t end
Definition: tessellator_libtess.cc:63

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
| ) |  |
| inline override protected virtual

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
| ) |  |
| inline override protected virtual

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 | ) |
| inline protected

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 }
impeller::Scalar
float Scalar
Definition: scalar.h:19

References RecordCurveSegment().

## ◆ RecordCurveSegment()

void impeller::StrokePathSegmentReceiver::RecordCurveSegment | ( | const SeparatedVector2 & | prev_perpendicular,
|  | const Point | cur,
|  | const SeparatedVector2 & | cur_perpendicular
| ) |  |
| inline protected

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 }
impeller::Join::kRound
@ kRound

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

Referenced by RecordArc(), and RecordCurve().

## ◆ RecordLine()

void impeller::StrokePathSegmentReceiver::RecordLine | ( | Point | p1,
|  | Point | p2
| ) |  |
| inline override protected virtual

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
| ) |  |
| inline override protected virtual

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:

- impeller/entity/geometry/stroke_path_geometry.cc