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