sugoi@google.com | e3453cb | 2013-01-07 14:26:40 +0000 | [diff] [blame] | 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 | |
| 14 | namespace { |
| 15 | |
| 16 | bool is_clockwise(const SkVector& before, const SkVector& after) { |
| 17 | return before.cross(after) > 0; |
| 18 | } |
| 19 | |
| 20 | enum IntersectionType { |
| 21 | kNone_IntersectionType, |
| 22 | kIn_IntersectionType, |
| 23 | kOut_IntersectionType |
| 24 | }; |
| 25 | |
| 26 | IntersectionType 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.com | 4e8ef33 | 2013-01-08 02:01:29 +0000 | [diff] [blame] | 33 | |
sugoi@google.com | e3453cb | 2013-01-07 14:26:40 +0000 | [diff] [blame] | 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 | } |
skia.committer@gmail.com | 4e8ef33 | 2013-01-08 02:01:29 +0000 | [diff] [blame] | 39 | |
sugoi@google.com | e3453cb | 2013-01-07 14:26:40 +0000 | [diff] [blame] | 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 | |
| 57 | GrStrokePathRenderer::GrStrokePathRenderer() { |
| 58 | } |
| 59 | |
| 60 | bool 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.com | 4e8ef33 | 2013-01-08 02:01:29 +0000 | [diff] [blame] | 75 | |
sugoi@google.com | e3453cb | 2013-01-07 14:26:40 +0000 | [diff] [blame] | 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 | |
| 86 | bool 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 |
sugoi@google.com | e3453cb | 2013-01-07 14:26:40 +0000 | [diff] [blame] | 114 | const int extraVerts = isMiter || isBevel ? 1 : 0; |
| 115 | const int maxVertexCount = nbQuads * (4 + extraVerts); |
| 116 | const int maxIndexCount = nbQuads * (6 + extraVerts * 3); // Each extra vert adds a triangle |
jvanverth@google.com | 9b855c7 | 2013-03-01 18:21:22 +0000 | [diff] [blame^] | 117 | target->drawState()->setDefaultVertexAttribs(); |
| 118 | GrDrawTarget::AutoReleaseGeometry arg(target, maxVertexCount, maxIndexCount); |
sugoi@google.com | e3453cb | 2013-01-07 14:26:40 +0000 | [diff] [blame] | 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]; |
jvanverth@google.com | 9b855c7 | 2013-03-01 18:21:22 +0000 | [diff] [blame^] | 129 | const SkScalar radius = SkScalarMul(width, 0.5f); |
sugoi@google.com | e3453cb | 2013-01-07 14:26:40 +0000 | [diff] [blame] | 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 | } |