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