• 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 #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