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