blob: 4d2c8a88628b5ab164f8320a01035f297a2af95c [file] [log] [blame]
sugoi@google.come3453cb2013-01-07 14:26:40 +00001/*
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();
skia.committer@gmail.com4e8ef332013-01-08 02:01:29 +000033
sugoi@google.come3453cb2013-01-07 14:26:40 +000034 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 }
skia.committer@gmail.com4e8ef332013-01-08 02:01:29 +000039
sugoi@google.come3453cb2013-01-07 14:26:40 +000040 // 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;
skia.committer@gmail.com4e8ef332013-01-08 02:01:29 +000075
sugoi@google.come3453cb2013-01-07 14:26:40 +000076 // 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