Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
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) {
43 SkPathPriv::Iterate it(path);
44 fIter = it.begin();
45 fEnd = it.end();
46 }
47
48 enum class Verb {
49 // Verbs that describe stroke geometry.
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.
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.
Type::kYUV Type::kRGBA() int(0.7 *637)
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
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]
SkPath::RangeIter end()
Definition SkPathPriv.h:187
SkPath::RangeIter begin()
Definition SkPathPriv.h:186
bool isZero() const
float fX
x-axis value
float length() const
float fY
y-axis value