Flutter Engine
The Flutter Engine
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Modules Pages
StrokeIterator.h
Go to the documentation of this file.
1/*
2 * Copyright 2020 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8#ifndef skgpu_tessellate_StrokeIterator_DEFINED
9#define skgpu_tessellate_StrokeIterator_DEFINED
10
17#include "src/core/SkPathPriv.h"
18
19#include <array>
20
21class SkPath;
22
23namespace skgpu::tess {
24
25// This class iterates over the stroke geometry defined by a path and stroke. It automatically
26// converts closes and square caps to lines, and round caps to circles so the user doesn't have to
27// worry about it. At each location it provides a verb and "prevVerb" so there is context about the
28// preceding join. Usage:
29//
30// StrokeIterator iter(path, stroke);
31// while (iter.next()) { // Call next() first.
32// iter.verb();
33// iter.pts();
34// iter.w();
35// iter.prevVerb();
36// iter.prevPts();
37// }
38//
40public:
41 StrokeIterator(const SkPath& path, const SkStrokeRec* stroke, const SkMatrix* viewMatrix)
42 : fViewMatrix(viewMatrix), fStroke(stroke) {
44 fIter = it.begin();
45 fEnd = it.end();
46 }
47
48 enum class Verb {
49 // Verbs that describe stroke geometry.
50 kLine = (int)SkPathVerb::kLine,
53 kCubic = (int)SkPathVerb::kCubic,
54 kCircle, // A stroke-width circle drawn as a 180-degree point stroke.
55
56 // Helper verbs that notify callers to update their own iteration state.
57 kMoveWithinContour,
58 kContourFinished
59 };
60 constexpr static bool IsVerbGeometric(Verb verb) { return verb < Verb::kMoveWithinContour; }
61
62 // Must be called first. Loads the next pair of "prev" and "current" stroke. Returns false if
63 // iteration is complete.
64 bool next() {
65 if (fQueueCount) {
66 SkASSERT(fQueueCount >= 2);
67 this->popFront();
68 if (fQueueCount >= 2) {
69 return true;
70 }
71 SkASSERT(fQueueCount == 1);
72 if (this->atVerb(0) == Verb::kContourFinished) {
73 // Don't let "kContourFinished" be prevVerb at the start of the next contour.
74 fQueueCount = 0;
75 }
76 }
77 for (; fIter != fEnd; ++fIter) {
78 SkASSERT(fQueueCount == 0 || fQueueCount == 1);
79 auto [verb, pts, w] = *fIter;
80 switch (verb) {
82 if (!this->finishOpenContour()) {
83 continue;
84 }
85 break;
87 if (pts[3] == pts[2]) {
88 [[fallthrough]]; // i.e., "if (p3 == p2 && p2 == p1 && p1 == p0)"
91 if (pts[2] == pts[1]) {
92 [[fallthrough]]; // i.e., "if (p2 == p1 && p1 == p0)"
94 if (pts[1] == pts[0]) {
95 fLastDegenerateStrokePt = pts;
96 continue;
97 }}}
98 this->enqueue((Verb)verb, pts, w);
99 if (fQueueCount == 1) {
100 // Defer the first verb until the end when we know what it's joined to.
101 fFirstVerbInContour = (Verb)verb;
102 fFirstPtsInContour = pts;
103 fFirstWInContour = w;
104 continue;
105 }
106 break;
108 if (!fQueueCount) {
109 fLastDegenerateStrokePt = pts;
110 continue;
111 }
112 if (pts[0] != fFirstPtsInContour[0]) {
113 // Draw a line back to the contour's starting point.
114 fClosePts = {pts[0], fFirstPtsInContour[0]};
115 this->enqueue(Verb::kLine, fClosePts.data(), nullptr);
116 }
117 // Repeat the first verb, this time as the "current" stroke instead of the prev.
118 this->enqueue(fFirstVerbInContour, fFirstPtsInContour, fFirstWInContour);
119 this->enqueue(Verb::kContourFinished, nullptr, nullptr);
120 fLastDegenerateStrokePt = nullptr;
121 break;
122 }
123 SkASSERT(fQueueCount >= 2);
124 ++fIter;
125 return true;
126 }
127 return this->finishOpenContour();
128 }
129
130 Verb prevVerb() const { return this->atVerb(0); }
131 const SkPoint* prevPts() const { return this->atPts(0); }
132
133 Verb verb() const { return this->atVerb(1); }
134 const SkPoint* pts() const { return this->atPts(1); }
135 float w() const { return this->atW(1); }
136
137 Verb firstVerbInContour() const { SkASSERT(fQueueCount > 0); return fFirstVerbInContour; }
138 const SkPoint* firstPtsInContour() const {
139 SkASSERT(fQueueCount > 0);
140 return fFirstPtsInContour;
141 }
142
143private:
144 constexpr static int kQueueBufferCount = 8;
145 Verb atVerb(int i) const {
146 SkASSERT(0 <= i && i < fQueueCount);
147 return fVerbs[(fQueueFrontIdx + i) & (kQueueBufferCount - 1)];
148 }
149 Verb backVerb() const {
150 return this->atVerb(fQueueCount - 1);
151 }
152 const SkPoint* atPts(int i) const {
153 SkASSERT(0 <= i && i < fQueueCount);
154 return fPts[(fQueueFrontIdx + i) & (kQueueBufferCount - 1)];
155 }
156 const SkPoint* backPts() const {
157 return this->atPts(fQueueCount - 1);
158 }
159 float atW(int i) const {
160 SkASSERT(0 <= i && i < fQueueCount);
161 const float* w = fW[(fQueueFrontIdx + i) & (kQueueBufferCount - 1)];
162 SkASSERT(w);
163 return *w;
164 }
165 void enqueue(Verb verb, const SkPoint* pts, const float* w) {
166 SkASSERT(fQueueCount < kQueueBufferCount);
167 int i = (fQueueFrontIdx + fQueueCount) & (kQueueBufferCount - 1);
168 fVerbs[i] = verb;
169 fPts[i] = pts;
170 fW[i] = w;
171 ++fQueueCount;
172 }
173 void popFront() {
174 SkASSERT(fQueueCount > 0);
175 ++fQueueFrontIdx;
176 --fQueueCount;
177 }
178
179 // Finishes the current contour without closing it. Enqueues any necessary caps as well as the
180 // contour's first stroke that we deferred at the beginning.
181 // Returns false and makes no changes if the current contour was already finished.
182 bool finishOpenContour() {
183 if (fQueueCount) {
184 SkASSERT(this->backVerb() == Verb::kLine || this->backVerb() == Verb::kQuad ||
185 this->backVerb() == Verb::kConic || this->backVerb() == Verb::kCubic);
186 switch (fStroke->getCap()) {
188 // There are no caps, but inject a "move" so the first stroke doesn't get joined
189 // with the end of the contour when it's processed.
190 this->enqueue(Verb::kMoveWithinContour, fFirstPtsInContour, fFirstWInContour);
191 break;
192 case SkPaint::kRound_Cap: {
193 // The "kCircle" verb serves as our barrier to prevent the first stroke from
194 // getting joined with the end of the contour. We just need to make sure that
195 // the first point of the contour goes last.
196 int backIdx = SkPathPriv::PtsInIter((unsigned)this->backVerb()) - 1;
197 this->enqueue(Verb::kCircle, this->backPts() + backIdx, nullptr);
198 this->enqueue(Verb::kCircle, fFirstPtsInContour, fFirstWInContour);
199 break;
200 }
202 this->fillSquareCapPoints(); // Fills in fEndingCapPts and fBeginningCapPts.
203 // Append the ending cap onto the current contour.
204 this->enqueue(Verb::kLine, fEndingCapPts.data(), nullptr);
205 // Move to the beginning cap and append it right before (and joined to) the
206 // first stroke (that we will add below).
207 this->enqueue(Verb::kMoveWithinContour, fBeginningCapPts.data(), nullptr);
208 this->enqueue(Verb::kLine, fBeginningCapPts.data(), nullptr);
209 break;
210 }
211 } else if (fLastDegenerateStrokePt) {
212 // fQueueCount=0 means this subpath is zero length. Generates caps on its location.
213 //
214 // "Any zero length subpath ... shall be stroked if the 'stroke-linecap' property has
215 // a value of round or square producing respectively a circle or a square."
216 //
217 // (https://www.w3.org/TR/SVG11/painting.html#StrokeProperties)
218 //
219 switch (fStroke->getCap()) {
221 // Zero-length contour with butt caps. There are no caps and no first stroke to
222 // generate.
223 return false;
225 this->enqueue(Verb::kCircle, fLastDegenerateStrokePt, nullptr);
226 // Setting the "first" stroke as the circle causes it to be added again below,
227 // this time as the "current" stroke.
228 fFirstVerbInContour = Verb::kCircle;
229 fFirstPtsInContour = fLastDegenerateStrokePt;
230 fFirstWInContour = nullptr;
231 break;
234 if (!fStroke->isHairlineStyle()) {
235 // Implement degenerate square caps as a stroke-width square in path space.
236 outset = {fStroke->getWidth() * .5f, 0};
237 } else {
238 // If the stroke is hairline, draw a 1x1 device-space square instead. This
239 // is equivalent to using:
240 //
241 // outset = inverse(fViewMatrix).mapVector(.5, 0)
242 //
243 // And since the matrix cannot have perspective, we only need to invert the
244 // upper 2x2 of the viewMatrix to achieve this.
245 SkASSERT(!fViewMatrix->hasPerspective());
246 float a=fViewMatrix->getScaleX(), b=fViewMatrix->getSkewX(),
247 c=fViewMatrix->getSkewY(), d=fViewMatrix->getScaleY();
248 float det = a*d - b*c;
249 if (det > 0) {
250 // outset = inverse(|a b|) * |.5|
251 // |c d| | 0|
252 //
253 // == 1/det * | d -b| * |.5|
254 // |-c a| | 0|
255 //
256 // == | d| * .5/det
257 // |-c|
258 outset = SkVector{d, -c} * (.5f / det);
259 } else {
260 outset = {1, 0};
261 }
262 }
263 fEndingCapPts = {*fLastDegenerateStrokePt - outset,
264 *fLastDegenerateStrokePt + outset};
265 // Add the square first as the "prev" join.
266 this->enqueue(Verb::kLine, fEndingCapPts.data(), nullptr);
267 this->enqueue(Verb::kMoveWithinContour, fEndingCapPts.data(), nullptr);
268 // Setting the "first" stroke as the square causes it to be added again below,
269 // this time as the "current" stroke.
270 fFirstVerbInContour = Verb::kLine;
271 fFirstPtsInContour = fEndingCapPts.data();
272 fFirstWInContour = nullptr;
273 break;
274 }
275 }
276 } else {
277 // This contour had no lines, beziers, or "close" verbs. There are no caps and no first
278 // stroke to generate.
279 return false;
280 }
281
282 // Repeat the first verb, this time as the "current" stroke instead of the prev.
283 this->enqueue(fFirstVerbInContour, fFirstPtsInContour, fFirstWInContour);
284 this->enqueue(Verb::kContourFinished, nullptr, nullptr);
285 fLastDegenerateStrokePt = nullptr;
286 return true;
287 }
288
289 // We implement square caps as two extra "kLine" verbs. This method finds the endpoints for
290 // those lines.
291 void fillSquareCapPoints() {
292 // Find the endpoints of the cap at the end of the contour.
293 SkVector lastTangent;
294 const SkPoint* lastPts = this->backPts();
295 Verb lastVerb = this->backVerb();
296 switch (lastVerb) {
297 case Verb::kCubic:
298 lastTangent = lastPts[3] - lastPts[2];
299 if (!lastTangent.isZero()) {
300 break;
301 }
302 [[fallthrough]];
303 case Verb::kConic:
304 case Verb::kQuad:
305 lastTangent = lastPts[2] - lastPts[1];
306 if (!lastTangent.isZero()) {
307 break;
308 }
309 [[fallthrough]];
310 case Verb::kLine:
311 lastTangent = lastPts[1] - lastPts[0];
312 SkASSERT(!lastTangent.isZero());
313 break;
314 default:
316 }
317 if (!fStroke->isHairlineStyle()) {
318 // Extend the cap by 1/2 stroke width.
319 lastTangent *= (.5f * fStroke->getWidth()) / lastTangent.length();
320 } else {
321 // Extend the cap by what will be 1/2 pixel after transformation.
322 lastTangent *= .5f / fViewMatrix->mapVector(lastTangent.fX, lastTangent.fY).length();
323 }
324 SkPoint lastPoint = lastPts[SkPathPriv::PtsInIter((unsigned)lastVerb) - 1];
325 fEndingCapPts = {lastPoint, lastPoint + lastTangent};
326
327 // Find the endpoints of the cap at the beginning of the contour.
328 SkVector firstTangent = fFirstPtsInContour[1] - fFirstPtsInContour[0];
329 if (firstTangent.isZero()) {
330 SkASSERT(fFirstVerbInContour == Verb::kQuad || fFirstVerbInContour == Verb::kConic ||
331 fFirstVerbInContour == Verb::kCubic);
332 firstTangent = fFirstPtsInContour[2] - fFirstPtsInContour[0];
333 if (firstTangent.isZero()) {
334 SkASSERT(fFirstVerbInContour == Verb::kCubic);
335 firstTangent = fFirstPtsInContour[3] - fFirstPtsInContour[0];
336 SkASSERT(!firstTangent.isZero());
337 }
338 }
339 if (!fStroke->isHairlineStyle()) {
340 // Set the the cap back by 1/2 stroke width.
341 firstTangent *= (-.5f * fStroke->getWidth()) / firstTangent.length();
342 } else {
343 // Set the cap back by what will be 1/2 pixel after transformation.
344 firstTangent *=
345 -.5f / fViewMatrix->mapVector(firstTangent.fX, firstTangent.fY).length();
346 }
347 fBeginningCapPts = {fFirstPtsInContour[0] + firstTangent, fFirstPtsInContour[0]};
348 }
349
350 // Info and iterators from the original path.
351 const SkMatrix* const fViewMatrix; // For hairlines.
352 const SkStrokeRec* const fStroke;
355
356 // Info for the current contour we are iterating.
357 Verb fFirstVerbInContour;
358 const SkPoint* fFirstPtsInContour;
359 const float* fFirstWInContour;
360 const SkPoint* fLastDegenerateStrokePt = nullptr;
361
362 // The queue is implemented as a roll-over array with a floating front index.
363 Verb fVerbs[kQueueBufferCount];
364 const SkPoint* fPts[kQueueBufferCount];
365 const float* fW[kQueueBufferCount];
366 int fQueueFrontIdx = 0;
367 int fQueueCount = 0;
368
369 // Storage space for geometry that gets defined implicitly by the path, but does not have
370 // actual points in memory to reference.
371 std::array<SkPoint, 2> fClosePts;
372 std::array<SkPoint, 2> fEndingCapPts;
373 std::array<SkPoint, 2> fBeginningCapPts;
374};
375
376} // namespace skgpu::tess
377
378#endif // skgpu_tessellate_StrokeIterator_DEFINED
static const int outset
Definition: BlurTest.cpp:58
#define SkUNREACHABLE
Definition: SkAssert.h:135
#define SkASSERT(cond)
Definition: SkAssert.h:116
@ kClose
SkPath::RawIter returns 0 points.
@ kCubic
SkPath::RawIter returns 4 points.
@ kConic
SkPath::RawIter returns 3 points + 1 weight.
@ kQuad
SkPath::RawIter returns 3 points.
@ kMove
SkPath::RawIter returns 1 point.
@ kLine
SkPath::RawIter returns 2 points.
SkScalar getSkewY() const
Definition: SkMatrix.h:430
SkScalar getSkewX() const
Definition: SkMatrix.h:438
SkScalar getScaleX() const
Definition: SkMatrix.h:415
SkScalar getScaleY() const
Definition: SkMatrix.h:422
bool hasPerspective() const
Definition: SkMatrix.h:312
void mapVector(SkScalar dx, SkScalar dy, SkVector *result) const
Definition: SkMatrix.h:1524
@ kRound_Cap
adds circle
Definition: SkPaint.h:335
@ kButt_Cap
no stroke extension
Definition: SkPaint.h:334
@ kSquare_Cap
adds square
Definition: SkPaint.h:336
static int PtsInIter(unsigned verb)
Definition: SkPathPriv.h:305
SkPath::RangeIter RangeIter
Definition: SkPathPriv.h:164
Definition: SkPath.h:59
bool isHairlineStyle() const
Definition: SkStrokeRec.h:47
SkScalar getWidth() const
Definition: SkStrokeRec.h:42
SkPaint::Cap getCap() const
Definition: SkStrokeRec.h:44
const SkPoint * prevPts() const
const SkPoint * pts() const
StrokeIterator(const SkPath &path, const SkStrokeRec *stroke, const SkMatrix *viewMatrix)
static constexpr bool IsVerbGeometric(Verb verb)
const SkPoint * firstPtsInContour() const
VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE auto & d
Definition: main.cc:19
static bool b
struct MyStruct a[10]
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir path
Definition: switches.h:57
const SkPoint kQuad[4]
SkPath::RangeIter end()
Definition: SkPathPriv.h:187
SkPath::RangeIter begin()
Definition: SkPathPriv.h:186
bool isZero() const
Definition: SkPoint_impl.h:193
float fX
x-axis value
Definition: SkPoint_impl.h:164
float length() const
Definition: SkPoint_impl.h:282
float fY
y-axis value
Definition: SkPoint_impl.h:165