GrStrokePathRenderer.cpp revision af3a3b9fb1f3be46082013a2d1977d12faf1f61c
1/*
2 * Copyright 2012 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 "GrStrokePathRenderer.h"
9
10#include "GrDrawTarget.h"
11#include "SkPath.h"
12#include "SkStrokeRec.h"
13
14namespace {
15
16bool is_clockwise(const SkVector& before, const SkVector& after) {
17    return before.cross(after) > 0;
18}
19
20enum IntersectionType {
21    kNone_IntersectionType,
22    kIn_IntersectionType,
23    kOut_IntersectionType
24};
25
26IntersectionType intersection(const SkPoint& p1, const SkPoint& p2,
27                              const SkPoint& p3, const SkPoint& p4,
28                                    SkPoint& res) {
29    // Store the values for fast access and easy
30    // equations-to-code conversion
31    SkScalar x1 = p1.x(), x2 = p2.x(), x3 = p3.x(), x4 = p4.x();
32    SkScalar y1 = p1.y(), y2 = p2.y(), y3 = p3.y(), y4 = p4.y();
33
34    SkScalar d = SkScalarMul(x1 - x2, y3 - y4) - SkScalarMul(y1 - y2, x3 - x4);
35    // If d is zero, there is no intersection
36    if (SkScalarNearlyZero(d)) {
37        return kNone_IntersectionType;
38    }
39
40    // Get the x and y
41    SkScalar pre  = SkScalarMul(x1, y2) - SkScalarMul(y1, x2),
42             post = SkScalarMul(x3, y4) - SkScalarMul(y3, x4);
43    // Compute the point of intersection
44    res.set(SkScalarDiv(SkScalarMul(pre, x3 - x4) - SkScalarMul(x1 - x2, post), d),
45            SkScalarDiv(SkScalarMul(pre, y3 - y4) - SkScalarMul(y1 - y2, post), d));
46
47    // Check if the x and y coordinates are within both lines
48    return (res.x() < GrMin(x1, x2) || res.x() > GrMax(x1, x2) ||
49            res.x() < GrMin(x3, x4) || res.x() > GrMax(x3, x4) ||
50            res.y() < GrMin(y1, y2) || res.y() > GrMax(y1, y2) ||
51            res.y() < GrMin(y3, y4) || res.y() > GrMax(y3, y4)) ?
52            kOut_IntersectionType : kIn_IntersectionType;
53}
54
55} // namespace
56
57GrStrokePathRenderer::GrStrokePathRenderer() {
58}
59
60bool GrStrokePathRenderer::canDrawPath(const SkPath& path,
61                                       const SkStrokeRec& stroke,
62                                       const GrDrawTarget* target,
63                                       bool antiAlias) const {
64    // FIXME : put the proper condition once GrDrawTarget::isOpaque is implemented
65    const bool isOpaque = true; // target->isOpaque();
66
67    // FIXME : remove this requirement once we have AA circles and implement the
68    //         circle joins/caps appropriately in the ::onDrawPath() function.
69    const bool requiresAACircle = (stroke.getCap()  == SkPaint::kRound_Cap) ||
70                                  (stroke.getJoin() == SkPaint::kRound_Join);
71
72    // Indices being stored in uint16, we don't want to overflow the indices capacity
73    static const int maxVBSize = 1 << 16;
74    const int maxNbVerts = (path.countPoints() + 1) * 5;
75
76    // Check that the path contains no curved lines, only straight lines
77    static const uint32_t unsupportedMask = SkPath::kQuad_SegmentMask | SkPath::kCubic_SegmentMask;
78
79    // Must not be filled nor hairline nor semi-transparent
80    // Note : May require a check to path.isConvex() if AA is supported
81    return ((stroke.getStyle() == SkStrokeRec::kStroke_Style) && (maxNbVerts < maxVBSize) &&
82            !path.isInverseFillType() && isOpaque && !requiresAACircle && !antiAlias &&
83            ((path.getSegmentMasks() & unsupportedMask) == 0));
84}
85
86bool GrStrokePathRenderer::onDrawPath(const SkPath& origPath,
87                                      const SkStrokeRec& stroke,
88                                      GrDrawTarget* target,
89                                      bool antiAlias) {
90    if (origPath.isEmpty()) {
91        return true;
92    }
93
94    SkScalar width = stroke.getWidth();
95    if (width <= 0) {
96        return false;
97    }
98
99    // Get the join type
100    SkPaint::Join join = stroke.getJoin();
101    SkScalar miterLimit = stroke.getMiter();
102    SkScalar sqMiterLimit = SkScalarMul(miterLimit, miterLimit);
103    if ((join == SkPaint::kMiter_Join) && (miterLimit <= SK_Scalar1)) {
104        // If the miter limit is small, treat it as a bevel join
105        join = SkPaint::kBevel_Join;
106    }
107    const bool isMiter       = (join == SkPaint::kMiter_Join);
108    const bool isBevel       = (join == SkPaint::kBevel_Join);
109    SkScalar invMiterLimit   = isMiter ? SK_Scalar1 / miterLimit : 0;
110    SkScalar invMiterLimitSq = SkScalarMul(invMiterLimit, invMiterLimit);
111
112    // Allocate vertices
113    const int nbQuads     = origPath.countPoints() + 1; // Could be "-1" if path is not closed
114    GrVertexLayout layout = 0; // Just 3D points
115    const int extraVerts  = isMiter || isBevel ? 1 : 0;
116    const int maxVertexCount = nbQuads * (4 + extraVerts);
117    const int maxIndexCount  = nbQuads * (6 + extraVerts * 3); // Each extra vert adds a triangle
118    GrDrawTarget::AutoReleaseGeometry arg(target, layout, maxVertexCount, maxIndexCount);
119    if (!arg.succeeded()) {
120        return false;
121    }
122    SkPoint* verts = reinterpret_cast<SkPoint*>(arg.vertices());
123    uint16_t* idxs = reinterpret_cast<uint16_t*>(arg.indices());
124    int vCount = 0, iCount = 0;
125
126    // Transform the path into a list of triangles
127    SkPath::Iter iter(origPath, false);
128    SkPoint pts[4];
129    const SkScalar radius = SkScalarMul(width, 0.5);
130    SkPoint *firstPt = verts, *lastPt = NULL;
131    SkVector firstDir, dir;
132    firstDir.set(0, 0);
133    dir.set(0, 0);
134    bool isOpen = true;
135    for(SkPath::Verb v = iter.next(pts); v != SkPath::kDone_Verb; v = iter.next(pts)) {
136        switch(v) {
137            case SkPath::kMove_Verb:
138                // This will already be handled as pts[0] of the 1st line
139                break;
140            case SkPath::kClose_Verb:
141                isOpen = (lastPt == NULL);
142                break;
143            case SkPath::kLine_Verb:
144            {
145                SkVector v0 = dir;
146                dir = pts[1] - pts[0];
147                if (dir.setLength(radius)) {
148                    SkVector dirT;
149                    dirT.set(dir.fY, -dir.fX); // Get perpendicular direction
150                    SkPoint l1a = pts[0]+dirT, l1b = pts[1]+dirT,
151                            l2a = pts[0]-dirT, l2b = pts[1]-dirT;
152                    SkPoint miterPt[2];
153                    bool useMiterPoint = false;
154                    int idx0(-1), idx1(-1);
155                    if (NULL == lastPt) {
156                        firstDir = dir;
157                    } else {
158                        SkVector v1 = dir;
159                        if (v0.normalize() && v1.normalize()) {
160                            SkScalar dotProd = v0.dot(v1);
161                            // No need for bevel or miter join if the angle
162                            // is either 0 or 180 degrees
163                            if (!SkScalarNearlyZero(dotProd + SK_Scalar1) &&
164                                !SkScalarNearlyZero(dotProd - SK_Scalar1)) {
165                                bool ccw = !is_clockwise(v0, v1);
166                                int offset = ccw ? 1 : 0;
167                                idx0 = vCount-2+offset;
168                                idx1 = vCount+offset;
169                                const SkPoint* pt0 = &(lastPt[offset]);
170                                const SkPoint* pt1 = ccw ? &l2a : &l1a;
171                                switch(join) {
172                                    case SkPaint::kMiter_Join:
173                                    {
174                                        // *Note : Logic is from MiterJoiner
175
176                                        // FIXME : Special case if we have a right angle ?
177                                        // if (SkScalarNearlyZero(dotProd)) {...}
178
179                                        SkScalar sinHalfAngleSq =
180                                                SkScalarHalf(SK_Scalar1 + dotProd);
181                                        if (sinHalfAngleSq >= invMiterLimitSq) {
182                                            // Find the miter point (or points if it is further
183                                            // than the miter limit)
184                                            const SkPoint pt2 = *pt0+v0, pt3 = *pt1+v1;
185                                            if (intersection(*pt0, pt2, *pt1, pt3, miterPt[0]) !=
186                                                kNone_IntersectionType) {
187                                                SkPoint miterPt0 = miterPt[0] - *pt0;
188                                                SkPoint miterPt1 = miterPt[0] - *pt1;
189                                                SkScalar sqDist0 = miterPt0.dot(miterPt0);
190                                                SkScalar sqDist1 = miterPt1.dot(miterPt1);
191                                                const SkScalar rSq =
192                                                        SkScalarDiv(SkScalarMul(radius, radius),
193                                                                    sinHalfAngleSq);
194                                                const SkScalar sqRLimit =
195                                                        SkScalarMul(sqMiterLimit, rSq);
196                                                if (sqDist0 > sqRLimit || sqDist1 > sqRLimit) {
197                                                    if (sqDist1 > sqRLimit) {
198                                                        v1.setLength(SkScalarSqrt(sqRLimit));
199                                                        miterPt[1] = *pt1+v1;
200                                                    } else {
201                                                        miterPt[1] = miterPt[0];
202                                                    }
203                                                    if (sqDist0 > sqRLimit) {
204                                                        v0.setLength(SkScalarSqrt(sqRLimit));
205                                                        miterPt[0] = *pt0+v0;
206                                                    }
207                                                } else {
208                                                    miterPt[1] = miterPt[0];
209                                                }
210                                                useMiterPoint = true;
211                                            }
212                                        }
213                                        if (useMiterPoint && (miterPt[1] == miterPt[0])) {
214                                            break;
215                                        }
216                                    }
217                                    default:
218                                    case SkPaint::kBevel_Join:
219                                    {
220                                        // Note : This currently causes some overdraw where both
221                                        //        lines initially intersect. We'd need to add
222                                        //        another line intersection check here if the
223                                        //        overdraw becomes an issue instead of using the
224                                        //        current point directly.
225
226                                        // Add center point
227                                        *verts++ = pts[0]; // Use current point directly
228                                        // This idx is passed the current point so increment it
229                                        ++idx1;
230                                        // Add center triangle
231                                        *idxs++ = idx0;
232                                        *idxs++ = vCount;
233                                        *idxs++ = idx1;
234                                        vCount++;
235                                        iCount += 3;
236                                    }
237                                    break;
238                                }
239                            }
240                        }
241                    }
242                    *verts++ = l1a;
243                    *verts++ = l2a;
244                    lastPt   = verts;
245                    *verts++ = l1b;
246                    *verts++ = l2b;
247
248                    if (useMiterPoint && (idx0 >= 0) && (idx1 >= 0)) {
249                        firstPt[idx0] = miterPt[0];
250                        firstPt[idx1] = miterPt[1];
251                    }
252
253                    // 1st triangle
254                    *idxs++  = vCount+0;
255                    *idxs++  = vCount+2;
256                    *idxs++  = vCount+1;
257                    // 2nd triangle
258                    *idxs++  = vCount+1;
259                    *idxs++  = vCount+2;
260                    *idxs++  = vCount+3;
261
262                    vCount += 4;
263                    iCount += 6;
264                }
265            }
266                break;
267            case SkPath::kQuad_Verb:
268            case SkPath::kCubic_Verb:
269                GrAssert(!"Curves not supported!");
270            default:
271                // Unhandled cases
272                GrAssert(false);
273        }
274    }
275
276    if (isOpen) {
277        // Add caps
278        switch (stroke.getCap()) {
279            case SkPaint::kSquare_Cap:
280                firstPt[0] -= firstDir;
281                firstPt[1] -= firstDir;
282                lastPt [0] += dir;
283                lastPt [1] += dir;
284                break;
285            case SkPaint::kRound_Cap:
286                GrAssert(!"Round caps not supported!");
287            default: // No cap
288                break;
289        }
290    }
291
292    GrAssert(vCount <= maxVertexCount);
293    GrAssert(iCount <= maxIndexCount);
294
295    if (vCount > 0) {
296        target->drawIndexed(kTriangles_GrPrimitiveType,
297                            0,        // start vertex
298                            0,        // start index
299                            vCount,
300                            iCount);
301    }
302
303    return true;
304}
305