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#include "include/core/SkBitmap.h" 9#include "include/core/SkCanvas.h" 10#include "include/core/SkPath.h" 11#include "include/utils/SkParsePath.h" 12#include "samplecode/Sample.h" 13 14#include "src/core/SkGeometry.h" 15 16namespace { 17 18////////////////////////////////////////////////////////////////////////////// 19 20static SkPoint rotate90(const SkPoint& p) { return {p.fY, -p.fX}; } 21static SkPoint rotate180(const SkPoint& p) { return p * -1; } 22static SkPoint setLength(SkPoint p, float len) { 23 if (!p.setLength(len)) { 24 SkDebugf("Failed to set point length\n"); 25 } 26 return p; 27} 28static bool isClockwise(const SkPoint& a, const SkPoint& b) { return a.cross(b) > 0; } 29 30////////////////////////////////////////////////////////////////////////////// 31// Testing ground for a new stroker implementation 32 33/** Helper class for constructing paths, with undo support */ 34class PathRecorder { 35public: 36 SkPath getPath() const { 37 return SkPath::Make(fPoints.data(), fPoints.size(), fVerbs.data(), fVerbs.size(), nullptr, 38 0, SkPathFillType::kWinding); 39 } 40 41 void moveTo(SkPoint p) { 42 fVerbs.push_back(SkPath::kMove_Verb); 43 fPoints.push_back(p); 44 } 45 46 void lineTo(SkPoint p) { 47 fVerbs.push_back(SkPath::kLine_Verb); 48 fPoints.push_back(p); 49 } 50 51 void close() { fVerbs.push_back(SkPath::kClose_Verb); } 52 53 void rewind() { 54 fVerbs.clear(); 55 fPoints.clear(); 56 } 57 58 int countPoints() const { return fPoints.size(); } 59 60 int countVerbs() const { return fVerbs.size(); } 61 62 bool getLastPt(SkPoint* lastPt) const { 63 if (fPoints.empty()) { 64 return false; 65 } 66 *lastPt = fPoints.back(); 67 return true; 68 } 69 70 void setLastPt(SkPoint lastPt) { 71 if (fPoints.empty()) { 72 moveTo(lastPt); 73 } else { 74 fPoints.back().set(lastPt.fX, lastPt.fY); 75 } 76 } 77 78 const std::vector<uint8_t>& verbs() const { return fVerbs; } 79 80 const std::vector<SkPoint>& points() const { return fPoints; } 81 82private: 83 std::vector<uint8_t> fVerbs; 84 std::vector<SkPoint> fPoints; 85}; 86 87class SkPathStroker2 { 88public: 89 // Returns the fill path 90 SkPath getFillPath(const SkPath& path, const SkPaint& paint); 91 92private: 93 struct PathSegment { 94 SkPath::Verb fVerb; 95 SkPoint fPoints[4]; 96 }; 97 98 float fRadius; 99 SkPaint::Cap fCap; 100 SkPaint::Join fJoin; 101 PathRecorder fInner, fOuter; 102 103 // Initialize stroker state 104 void initForPath(const SkPath& path, const SkPaint& paint); 105 106 // Strokes a line segment 107 void strokeLine(const PathSegment& line, bool needsMove); 108 109 // Adds an endcap to fOuter 110 enum class CapLocation { Start, End }; 111 void endcap(CapLocation loc); 112 113 // Adds a join between the two segments 114 void join(const PathSegment& prev, const PathSegment& curr); 115 116 // Appends path in reverse to result 117 static void appendPathReversed(const PathRecorder& path, PathRecorder* result); 118 119 // Returns the segment unit normal 120 static SkPoint unitNormal(const PathSegment& seg, float t); 121 122 // Returns squared magnitude of line segments. 123 static float squaredLineLength(const PathSegment& lineSeg); 124}; 125 126void SkPathStroker2::initForPath(const SkPath& path, const SkPaint& paint) { 127 fRadius = paint.getStrokeWidth() / 2; 128 fCap = paint.getStrokeCap(); 129 fJoin = paint.getStrokeJoin(); 130 fInner.rewind(); 131 fOuter.rewind(); 132} 133 134SkPath SkPathStroker2::getFillPath(const SkPath& path, const SkPaint& paint) { 135 initForPath(path, paint); 136 137 // Trace the inner and outer paths simultaneously. Inner will therefore be 138 // recorded in reverse from how we trace the outline. 139 SkPath::Iter it(path, false); 140 PathSegment segment, prevSegment; 141 bool firstSegment = true; 142 while ((segment.fVerb = it.next(segment.fPoints)) != SkPath::kDone_Verb) { 143 // Join to the previous segment 144 if (!firstSegment) { 145 join(prevSegment, segment); 146 } 147 148 // Stroke the current segment 149 switch (segment.fVerb) { 150 case SkPath::kLine_Verb: 151 strokeLine(segment, firstSegment); 152 break; 153 case SkPath::kMove_Verb: 154 // Don't care about multiple contours currently 155 continue; 156 default: 157 SkDebugf("Unhandled path verb %d\n", segment.fVerb); 158 break; 159 } 160 161 std::swap(segment, prevSegment); 162 firstSegment = false; 163 } 164 165 // Open contour => endcap at the end 166 const bool isClosed = path.isLastContourClosed(); 167 if (isClosed) { 168 SkDebugf("Unhandled closed contour\n"); 169 } else { 170 endcap(CapLocation::End); 171 } 172 173 // Walk inner path in reverse, appending to result 174 appendPathReversed(fInner, &fOuter); 175 endcap(CapLocation::Start); 176 177 return fOuter.getPath(); 178} 179 180void SkPathStroker2::strokeLine(const PathSegment& line, bool needsMove) { 181 const SkPoint tangent = line.fPoints[1] - line.fPoints[0]; 182 const SkPoint normal = rotate90(tangent); 183 const SkPoint offset = setLength(normal, fRadius); 184 if (needsMove) { 185 fOuter.moveTo(line.fPoints[0] + offset); 186 fInner.moveTo(line.fPoints[0] - offset); 187 } 188 fOuter.lineTo(line.fPoints[1] + offset); 189 fInner.lineTo(line.fPoints[1] - offset); 190} 191 192void SkPathStroker2::endcap(CapLocation loc) { 193 const auto buttCap = [this](CapLocation loc) { 194 if (loc == CapLocation::Start) { 195 // Back at the start of the path: just close the stroked outline 196 fOuter.close(); 197 } else { 198 // Inner last pt == first pt when appending in reverse 199 SkPoint innerLastPt; 200 fInner.getLastPt(&innerLastPt); 201 fOuter.lineTo(innerLastPt); 202 } 203 }; 204 205 switch (fCap) { 206 case SkPaint::kButt_Cap: 207 buttCap(loc); 208 break; 209 default: 210 SkDebugf("Unhandled endcap %d\n", fCap); 211 buttCap(loc); 212 break; 213 } 214} 215 216void SkPathStroker2::join(const PathSegment& prev, const PathSegment& curr) { 217 const auto miterJoin = [this](const PathSegment& prev, const PathSegment& curr) { 218 // Common path endpoint of the two segments is the midpoint of the miter line. 219 const SkPoint miterMidpt = curr.fPoints[0]; 220 221 SkPoint before = unitNormal(prev, 1); 222 SkPoint after = unitNormal(curr, 0); 223 224 // Check who's inside and who's outside. 225 PathRecorder *outer = &fOuter, *inner = &fInner; 226 if (!isClockwise(before, after)) { 227 std::swap(inner, outer); 228 before = rotate180(before); 229 after = rotate180(after); 230 } 231 232 const float cosTheta = before.dot(after); 233 if (SkScalarNearlyZero(1 - cosTheta)) { 234 // Nearly identical normals: don't bother. 235 return; 236 } 237 238 // Before and after have the same origin and magnitude, so before+after is the diagonal of 239 // their rhombus. Origin of this vector is the midpoint of the miter line. 240 SkPoint miterVec = before + after; 241 242 // Note the relationship (draw a right triangle with the miter line as its hypoteneuse): 243 // sin(theta/2) = strokeWidth / miterLength 244 // so miterLength = strokeWidth / sin(theta/2) 245 // where miterLength is the length of the miter from outer point to inner corner. 246 // miterVec's origin is the midpoint of the miter line, so we use strokeWidth/2. 247 // Sqrt is just an application of half-angle identities. 248 const float sinHalfTheta = sqrtf(0.5 * (1 + cosTheta)); 249 const float halfMiterLength = fRadius / sinHalfTheta; 250 miterVec.setLength(halfMiterLength); // TODO: miter length limit 251 252 // Outer: connect to the miter point, and then to t=0 (on outside stroke) of next segment. 253 const SkPoint dest = setLength(after, fRadius); 254 outer->lineTo(miterMidpt + miterVec); 255 outer->lineTo(miterMidpt + dest); 256 257 // Inner miter is more involved. We're already at t=1 (on inside stroke) of 'prev'. 258 // Check 2 cases to see we can directly connect to the inner miter point 259 // (midpoint - miterVec), or if we need to add extra "loop" geometry. 260 const SkPoint prevUnitTangent = rotate90(before); 261 const float radiusSquared = fRadius * fRadius; 262 // 'alpha' is angle between prev tangent and the curr inwards normal 263 const float cosAlpha = prevUnitTangent.dot(-after); 264 // Solve triangle for len^2: radius^2 = len^2 + (radius * sin(alpha))^2 265 // This is the point at which the inside "corner" of curr at t=0 will lie on a 266 // line connecting the inner and outer corners of prev at t=0. If len is below 267 // this threshold, the inside corner of curr will "poke through" the start of prev, 268 // and we'll need the inner loop geometry. 269 const float threshold1 = radiusSquared * cosAlpha * cosAlpha; 270 // Solve triangle for len^2: halfMiterLen^2 = radius^2 + len^2 271 // This is the point at which the inner miter point will lie on the inner stroke 272 // boundary of the curr segment. If len is below this threshold, the miter point 273 // moves 'inside' of the stroked outline, and we'll need the inner loop geometry. 274 const float threshold2 = halfMiterLength * halfMiterLength - radiusSquared; 275 // If a segment length is smaller than the larger of the two thresholds, 276 // we'll have to add the inner loop geometry. 277 const float maxLenSqd = std::max(threshold1, threshold2); 278 const bool needsInnerLoop = 279 squaredLineLength(prev) < maxLenSqd || squaredLineLength(curr) < maxLenSqd; 280 if (needsInnerLoop) { 281 // Connect to the miter midpoint (common path endpoint of the two segments), 282 // and then to t=0 (on inside) of the next segment. This adds an interior "loop" of 283 // geometry that handles edge cases where segment lengths are shorter than the 284 // stroke width. 285 inner->lineTo(miterMidpt); 286 inner->lineTo(miterMidpt - dest); 287 } else { 288 // Directly connect to inner miter point. 289 inner->setLastPt(miterMidpt - miterVec); 290 } 291 }; 292 293 switch (fJoin) { 294 case SkPaint::kMiter_Join: 295 miterJoin(prev, curr); 296 break; 297 default: 298 SkDebugf("Unhandled join %d\n", fJoin); 299 miterJoin(prev, curr); 300 break; 301 } 302} 303 304void SkPathStroker2::appendPathReversed(const PathRecorder& path, PathRecorder* result) { 305 const int numVerbs = path.countVerbs(); 306 const int numPoints = path.countPoints(); 307 const std::vector<uint8_t>& verbs = path.verbs(); 308 const std::vector<SkPoint>& points = path.points(); 309 310 for (int i = numVerbs - 1, j = numPoints; i >= 0; i--) { 311 auto verb = static_cast<SkPath::Verb>(verbs[i]); 312 switch (verb) { 313 case SkPath::kLine_Verb: { 314 j -= 1; 315 SkASSERT(j >= 1); 316 result->lineTo(points[j - 1]); 317 break; 318 } 319 case SkPath::kMove_Verb: 320 // Ignore 321 break; 322 default: 323 SkASSERT(false); 324 break; 325 } 326 } 327} 328 329SkPoint SkPathStroker2::unitNormal(const PathSegment& seg, float t) { 330 if (seg.fVerb != SkPath::kLine_Verb) { 331 SkDebugf("Unhandled verb for unit normal %d\n", seg.fVerb); 332 } 333 334 (void)t; // Not needed for lines 335 const SkPoint tangent = seg.fPoints[1] - seg.fPoints[0]; 336 const SkPoint normal = rotate90(tangent); 337 return setLength(normal, 1); 338} 339 340float SkPathStroker2::squaredLineLength(const PathSegment& lineSeg) { 341 SkASSERT(lineSeg.fVerb == SkPath::kLine_Verb); 342 const SkPoint diff = lineSeg.fPoints[1] - lineSeg.fPoints[0]; 343 return diff.dot(diff); 344} 345 346} // namespace 347 348////////////////////////////////////////////////////////////////////////////// 349 350class SimpleStroker : public Sample { 351 bool fShowSkiaStroke, fShowHidden, fShowSkeleton; 352 float fWidth = 175; 353 SkPaint fPtsPaint, fStrokePaint, fMirrorStrokePaint, fNewFillPaint, fHiddenPaint, 354 fSkeletonPaint; 355 inline static constexpr int kN = 3; 356 357public: 358 SkPoint fPts[kN]; 359 360 SimpleStroker() : fShowSkiaStroke(true), fShowHidden(true), fShowSkeleton(true) { 361 fPts[0] = {500, 200}; 362 fPts[1] = {300, 200}; 363 fPts[2] = {100, 100}; 364 365 fPtsPaint.setAntiAlias(true); 366 fPtsPaint.setStrokeWidth(10); 367 fPtsPaint.setStrokeCap(SkPaint::kRound_Cap); 368 369 fStrokePaint.setAntiAlias(true); 370 fStrokePaint.setStyle(SkPaint::kStroke_Style); 371 fStrokePaint.setColor(0x80FF0000); 372 373 fMirrorStrokePaint.setAntiAlias(true); 374 fMirrorStrokePaint.setStyle(SkPaint::kStroke_Style); 375 fMirrorStrokePaint.setColor(0x80FFFF00); 376 377 fNewFillPaint.setAntiAlias(true); 378 fNewFillPaint.setColor(0x8000FF00); 379 380 fHiddenPaint.setAntiAlias(true); 381 fHiddenPaint.setStyle(SkPaint::kStroke_Style); 382 fHiddenPaint.setColor(0xFF0000FF); 383 384 fSkeletonPaint.setAntiAlias(true); 385 fSkeletonPaint.setStyle(SkPaint::kStroke_Style); 386 fSkeletonPaint.setColor(SK_ColorRED); 387 } 388 389 void toggle(bool& value) { value = !value; } 390 391protected: 392 SkString name() override { return SkString("SimpleStroker"); } 393 394 bool onChar(SkUnichar uni) override { 395 switch (uni) { 396 case '1': 397 this->toggle(fShowSkeleton); 398 return true; 399 case '2': 400 this->toggle(fShowSkiaStroke); 401 return true; 402 case '3': 403 this->toggle(fShowHidden); 404 return true; 405 case '-': 406 fWidth -= 5; 407 return true; 408 case '=': 409 fWidth += 5; 410 return true; 411 default: 412 break; 413 } 414 return false; 415 } 416 417 void makePath(SkPath* path) { 418 path->moveTo(fPts[0]); 419 for (int i = 1; i < kN; ++i) { 420 path->lineTo(fPts[i]); 421 } 422 } 423 424 void onDrawContent(SkCanvas* canvas) override { 425 canvas->drawColor(0xFFEEEEEE); 426 427 SkPath path; 428 this->makePath(&path); 429 430 fStrokePaint.setStrokeWidth(fWidth); 431 432 // The correct result 433 if (fShowSkiaStroke) { 434 canvas->drawPath(path, fStrokePaint); 435 } 436 437 // Simple stroker result 438 SkPathStroker2 stroker; 439 SkPath fillPath = stroker.getFillPath(path, fStrokePaint); 440 canvas->drawPath(fillPath, fNewFillPaint); 441 442 if (fShowHidden) { 443 canvas->drawPath(fillPath, fHiddenPaint); 444 } 445 if (fShowSkeleton) { 446 canvas->drawPath(path, fSkeletonPaint); 447 } 448 canvas->drawPoints(SkCanvas::kPoints_PointMode, kN, fPts, fPtsPaint); 449 450 // Draw a mirror but using Skia's stroker. 451 canvas->translate(0, 400); 452 fMirrorStrokePaint.setStrokeWidth(fWidth); 453 canvas->drawPath(path, fMirrorStrokePaint); 454 if (fShowHidden) { 455 SkPath hidden; 456 fStrokePaint.getFillPath(path, &hidden); 457 canvas->drawPath(hidden, fHiddenPaint); 458 } 459 if (fShowSkeleton) { 460 canvas->drawPath(path, fSkeletonPaint); 461 } 462 } 463 464 Sample::Click* onFindClickHandler(SkScalar x, SkScalar y, skui::ModifierKey modi) override { 465 const SkScalar tol = 4; 466 const SkRect r = SkRect::MakeXYWH(x - tol, y - tol, tol * 2, tol * 2); 467 for (int i = 0; i < kN; ++i) { 468 if (r.intersects(SkRect::MakeXYWH(fPts[i].fX, fPts[i].fY, 1, 1))) { 469 return new Click([this, i](Click* c) { 470 fPts[i] = c->fCurr; 471 return true; 472 }); 473 } 474 } 475 return nullptr; 476 } 477 478private: 479 using INHERITED = Sample; 480}; 481 482DEF_SAMPLE(return new SimpleStroker;) 483