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
16 namespace {
17
18 //////////////////////////////////////////////////////////////////////////////
19
rotate90(const SkPoint & p)20 static SkPoint rotate90(const SkPoint& p) { return {p.fY, -p.fX}; }
rotate180(const SkPoint & p)21 static SkPoint rotate180(const SkPoint& p) { return p * -1; }
setLength(SkPoint p,float len)22 static SkPoint setLength(SkPoint p, float len) {
23 if (!p.setLength(len)) {
24 SkDebugf("Failed to set point length\n");
25 }
26 return p;
27 }
isClockwise(const SkPoint & a,const SkPoint & b)28 static 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 */
34 class PathRecorder {
35 public:
getPath() const36 SkPath getPath() const {
37 return SkPath::Make(fPoints.data(), fPoints.size(), fVerbs.data(), fVerbs.size(), nullptr,
38 0, SkPathFillType::kWinding);
39 }
40
moveTo(SkPoint p)41 void moveTo(SkPoint p) {
42 fVerbs.push_back(SkPath::kMove_Verb);
43 fPoints.push_back(p);
44 }
45
lineTo(SkPoint p)46 void lineTo(SkPoint p) {
47 fVerbs.push_back(SkPath::kLine_Verb);
48 fPoints.push_back(p);
49 }
50
close()51 void close() { fVerbs.push_back(SkPath::kClose_Verb); }
52
rewind()53 void rewind() {
54 fVerbs.clear();
55 fPoints.clear();
56 }
57
countPoints() const58 int countPoints() const { return fPoints.size(); }
59
countVerbs() const60 int countVerbs() const { return fVerbs.size(); }
61
getLastPt(SkPoint * lastPt) const62 bool getLastPt(SkPoint* lastPt) const {
63 if (fPoints.empty()) {
64 return false;
65 }
66 *lastPt = fPoints.back();
67 return true;
68 }
69
setLastPt(SkPoint lastPt)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
verbs() const78 const std::vector<uint8_t>& verbs() const { return fVerbs; }
79
points() const80 const std::vector<SkPoint>& points() const { return fPoints; }
81
82 private:
83 std::vector<uint8_t> fVerbs;
84 std::vector<SkPoint> fPoints;
85 };
86
87 class SkPathStroker2 {
88 public:
89 // Returns the fill path
90 SkPath getFillPath(const SkPath& path, const SkPaint& paint);
91
92 private:
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
initForPath(const SkPath & path,const SkPaint & paint)126 void 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
getFillPath(const SkPath & path,const SkPaint & paint)134 SkPath 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
strokeLine(const PathSegment & line,bool needsMove)180 void 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
endcap(CapLocation loc)192 void 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
join(const PathSegment & prev,const PathSegment & curr)216 void 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
appendPathReversed(const PathRecorder & path,PathRecorder * result)304 void 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
unitNormal(const PathSegment & seg,float t)329 SkPoint 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
squaredLineLength(const PathSegment & lineSeg)340 float 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
350 class SimpleStroker : public Sample {
351 bool fShowSkiaStroke, fShowHidden, fShowSkeleton;
352 float fWidth = 175;
353 SkPaint fPtsPaint, fStrokePaint, fMirrorStrokePaint, fNewFillPaint, fHiddenPaint,
354 fSkeletonPaint;
355 static constexpr int kN = 3;
356
357 public:
358 SkPoint fPts[kN];
359
SimpleStroker()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
toggle(bool & value)389 void toggle(bool& value) { value = !value; }
390
391 protected:
name()392 SkString name() override { return SkString("SimpleStroker"); }
393
onChar(SkUnichar uni)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
makePath(SkPath * path)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
onDrawContent(SkCanvas * canvas)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
onFindClickHandler(SkScalar x,SkScalar y,skui::ModifierKey modi)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
478 private:
479 using INHERITED = Sample;
480 };
481
482 DEF_SAMPLE(return new SimpleStroker;)
483