aboutsummaryrefslogtreecommitdiffhomepage
path: root/experimental/StrokePathRenderer/GrStrokePathRenderer.cpp
blob: 4d2c8a88628b5ab164f8320a01035f297a2af95c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
/*
 * Copyright 2012 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "GrStrokePathRenderer.h"

#include "GrDrawTarget.h"
#include "SkPath.h"
#include "SkStrokeRec.h"

namespace {

bool is_clockwise(const SkVector& before, const SkVector& after) {
    return before.cross(after) > 0;
}

enum IntersectionType {
    kNone_IntersectionType,
    kIn_IntersectionType,
    kOut_IntersectionType
};

IntersectionType intersection(const SkPoint& p1, const SkPoint& p2,
                              const SkPoint& p3, const SkPoint& p4,
                                    SkPoint& res) {
    // Store the values for fast access and easy
    // equations-to-code conversion
    SkScalar x1 = p1.x(), x2 = p2.x(), x3 = p3.x(), x4 = p4.x();
    SkScalar y1 = p1.y(), y2 = p2.y(), y3 = p3.y(), y4 = p4.y();

    SkScalar d = SkScalarMul(x1 - x2, y3 - y4) - SkScalarMul(y1 - y2, x3 - x4);
    // If d is zero, there is no intersection
    if (SkScalarNearlyZero(d)) {
        return kNone_IntersectionType;
    }

    // Get the x and y
    SkScalar pre  = SkScalarMul(x1, y2) - SkScalarMul(y1, x2),
             post = SkScalarMul(x3, y4) - SkScalarMul(y3, x4);
    // Compute the point of intersection
    res.set(SkScalarDiv(SkScalarMul(pre, x3 - x4) - SkScalarMul(x1 - x2, post), d),
            SkScalarDiv(SkScalarMul(pre, y3 - y4) - SkScalarMul(y1 - y2, post), d));

    // Check if the x and y coordinates are within both lines
    return (res.x() < GrMin(x1, x2) || res.x() > GrMax(x1, x2) ||
            res.x() < GrMin(x3, x4) || res.x() > GrMax(x3, x4) ||
            res.y() < GrMin(y1, y2) || res.y() > GrMax(y1, y2) ||
            res.y() < GrMin(y3, y4) || res.y() > GrMax(y3, y4)) ?
            kOut_IntersectionType : kIn_IntersectionType;
}

} // namespace

GrStrokePathRenderer::GrStrokePathRenderer() {
}

bool GrStrokePathRenderer::canDrawPath(const SkPath& path,
                                       const SkStrokeRec& stroke,
                                       const GrDrawTarget* target,
                                       bool antiAlias) const {
    // FIXME : put the proper condition once GrDrawTarget::isOpaque is implemented
    const bool isOpaque = true; // target->isOpaque();

    // FIXME : remove this requirement once we have AA circles and implement the
    //         circle joins/caps appropriately in the ::onDrawPath() function.
    const bool requiresAACircle = (stroke.getCap()  == SkPaint::kRound_Cap) ||
                                  (stroke.getJoin() == SkPaint::kRound_Join);

    // Indices being stored in uint16, we don't want to overflow the indices capacity
    static const int maxVBSize = 1 << 16;
    const int maxNbVerts = (path.countPoints() + 1) * 5;

    // Check that the path contains no curved lines, only straight lines
    static const uint32_t unsupportedMask = SkPath::kQuad_SegmentMask | SkPath::kCubic_SegmentMask;

    // Must not be filled nor hairline nor semi-transparent
    // Note : May require a check to path.isConvex() if AA is supported
    return ((stroke.getStyle() == SkStrokeRec::kStroke_Style) && (maxNbVerts < maxVBSize) &&
            !path.isInverseFillType() && isOpaque && !requiresAACircle && !antiAlias &&
            ((path.getSegmentMasks() & unsupportedMask) == 0));
}

bool GrStrokePathRenderer::onDrawPath(const SkPath& origPath,
                                      const SkStrokeRec& stroke,
                                      GrDrawTarget* target,
                                      bool antiAlias) {
    if (origPath.isEmpty()) {
        return true;
    }

    SkScalar width = stroke.getWidth();
    if (width <= 0) {
        return false;
    }

    // Get the join type
    SkPaint::Join join = stroke.getJoin();
    SkScalar miterLimit = stroke.getMiter();
    SkScalar sqMiterLimit = SkScalarMul(miterLimit, miterLimit);
    if ((join == SkPaint::kMiter_Join) && (miterLimit <= SK_Scalar1)) {
        // If the miter limit is small, treat it as a bevel join
        join = SkPaint::kBevel_Join;
    }
    const bool isMiter       = (join == SkPaint::kMiter_Join);
    const bool isBevel       = (join == SkPaint::kBevel_Join);
    SkScalar invMiterLimit   = isMiter ? SK_Scalar1 / miterLimit : 0;
    SkScalar invMiterLimitSq = SkScalarMul(invMiterLimit, invMiterLimit);

    // Allocate vertices
    const int nbQuads     = origPath.countPoints() + 1; // Could be "-1" if path is not closed
    GrVertexLayout layout = 0; // Just 3D points
    const int extraVerts  = isMiter || isBevel ? 1 : 0;
    const int maxVertexCount = nbQuads * (4 + extraVerts);
    const int maxIndexCount  = nbQuads * (6 + extraVerts * 3); // Each extra vert adds a triangle
    GrDrawTarget::AutoReleaseGeometry arg(target, layout, maxVertexCount, maxIndexCount);
    if (!arg.succeeded()) {
        return false;
    }
    SkPoint* verts = reinterpret_cast<SkPoint*>(arg.vertices());
    uint16_t* idxs = reinterpret_cast<uint16_t*>(arg.indices());
    int vCount = 0, iCount = 0;

    // Transform the path into a list of triangles
    SkPath::Iter iter(origPath, false);
    SkPoint pts[4];
    const SkScalar radius = SkScalarMul(width, 0.5);
    SkPoint *firstPt = verts, *lastPt = NULL;
    SkVector firstDir, dir;
    firstDir.set(0, 0);
    dir.set(0, 0);
    bool isOpen = true;
    for(SkPath::Verb v = iter.next(pts); v != SkPath::kDone_Verb; v = iter.next(pts)) {
        switch(v) {
            case SkPath::kMove_Verb:
                // This will already be handled as pts[0] of the 1st line
                break;
            case SkPath::kClose_Verb:
                isOpen = (lastPt == NULL);
                break;
            case SkPath::kLine_Verb:
            {
                SkVector v0 = dir;
                dir = pts[1] - pts[0];
                if (dir.setLength(radius)) {
                    SkVector dirT;
                    dirT.set(dir.fY, -dir.fX); // Get perpendicular direction
                    SkPoint l1a = pts[0]+dirT, l1b = pts[1]+dirT,
                            l2a = pts[0]-dirT, l2b = pts[1]-dirT;
                    SkPoint miterPt[2];
                    bool useMiterPoint = false;
                    int idx0(-1), idx1(-1);
                    if (NULL == lastPt) {
                        firstDir = dir;
                    } else {
                        SkVector v1 = dir;
                        if (v0.normalize() && v1.normalize()) {
                            SkScalar dotProd = v0.dot(v1);
                            // No need for bevel or miter join if the angle
                            // is either 0 or 180 degrees
                            if (!SkScalarNearlyZero(dotProd + SK_Scalar1) &&
                                !SkScalarNearlyZero(dotProd - SK_Scalar1)) {
                                bool ccw = !is_clockwise(v0, v1);
                                int offset = ccw ? 1 : 0;
                                idx0 = vCount-2+offset;
                                idx1 = vCount+offset;
                                const SkPoint* pt0 = &(lastPt[offset]);
                                const SkPoint* pt1 = ccw ? &l2a : &l1a;
                                switch(join) {
                                    case SkPaint::kMiter_Join:
                                    {
                                        // *Note : Logic is from MiterJoiner

                                        // FIXME : Special case if we have a right angle ?
                                        // if (SkScalarNearlyZero(dotProd)) {...}

                                        SkScalar sinHalfAngleSq =
                                                SkScalarHalf(SK_Scalar1 + dotProd);
                                        if (sinHalfAngleSq >= invMiterLimitSq) {
                                            // Find the miter point (or points if it is further
                                            // than the miter limit)
                                            const SkPoint pt2 = *pt0+v0, pt3 = *pt1+v1;
                                            if (intersection(*pt0, pt2, *pt1, pt3, miterPt[0]) !=
                                                kNone_IntersectionType) {
                                                SkPoint miterPt0 = miterPt[0] - *pt0;
                                                SkPoint miterPt1 = miterPt[0] - *pt1;
                                                SkScalar sqDist0 = miterPt0.dot(miterPt0);
                                                SkScalar sqDist1 = miterPt1.dot(miterPt1);
                                                const SkScalar rSq =
                                                        SkScalarDiv(SkScalarMul(radius, radius),
                                                                    sinHalfAngleSq);
                                                const SkScalar sqRLimit =
                                                        SkScalarMul(sqMiterLimit, rSq);
                                                if (sqDist0 > sqRLimit || sqDist1 > sqRLimit) {
                                                    if (sqDist1 > sqRLimit) {
                                                        v1.setLength(SkScalarSqrt(sqRLimit));
                                                        miterPt[1] = *pt1+v1;
                                                    } else {
                                                        miterPt[1] = miterPt[0];
                                                    }
                                                    if (sqDist0 > sqRLimit) {
                                                        v0.setLength(SkScalarSqrt(sqRLimit));
                                                        miterPt[0] = *pt0+v0;
                                                    }
                                                } else {
                                                    miterPt[1] = miterPt[0];
                                                }
                                                useMiterPoint = true;
                                            }
                                        }
                                        if (useMiterPoint && (miterPt[1] == miterPt[0])) {
                                            break;
                                        }
                                    }
                                    default:
                                    case SkPaint::kBevel_Join:
                                    {
                                        // Note : This currently causes some overdraw where both
                                        //        lines initially intersect. We'd need to add
                                        //        another line intersection check here if the
                                        //        overdraw becomes an issue instead of using the
                                        //        current point directly.

                                        // Add center point
                                        *verts++ = pts[0]; // Use current point directly
                                        // This idx is passed the current point so increment it
                                        ++idx1;
                                        // Add center triangle
                                        *idxs++ = idx0;
                                        *idxs++ = vCount;
                                        *idxs++ = idx1;
                                        vCount++;
                                        iCount += 3;
                                    }
                                    break;
                                }
                            }
                        }
                    }
                    *verts++ = l1a;
                    *verts++ = l2a;
                    lastPt   = verts;
                    *verts++ = l1b;
                    *verts++ = l2b;

                    if (useMiterPoint && (idx0 >= 0) && (idx1 >= 0)) {
                        firstPt[idx0] = miterPt[0];
                        firstPt[idx1] = miterPt[1];
                    }

                    // 1st triangle
                    *idxs++  = vCount+0;
                    *idxs++  = vCount+2;
                    *idxs++  = vCount+1;
                    // 2nd triangle
                    *idxs++  = vCount+1;
                    *idxs++  = vCount+2;
                    *idxs++  = vCount+3;

                    vCount += 4;
                    iCount += 6;
                }
            }
                break;
            case SkPath::kQuad_Verb:
            case SkPath::kCubic_Verb:
                GrAssert(!"Curves not supported!");
            default:
                // Unhandled cases
                GrAssert(false);
        }
    }

    if (isOpen) {
        // Add caps
        switch (stroke.getCap()) {
            case SkPaint::kSquare_Cap:
                firstPt[0] -= firstDir;
                firstPt[1] -= firstDir;
                lastPt [0] += dir;
                lastPt [1] += dir;
                break;
            case SkPaint::kRound_Cap:
                GrAssert(!"Round caps not supported!");
            default: // No cap
                break;
        }
    }

    GrAssert(vCount <= maxVertexCount);
    GrAssert(iCount <= maxIndexCount);

    if (vCount > 0) {
        target->drawIndexed(kTriangles_GrPrimitiveType,
                            0,        // start vertex
                            0,        // start index
                            vCount,
                            iCount);
    }

    return true;
}