/* * 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" static bool is_clockwise(const SkVector& before, const SkVector& after) { return before.cross(after) > 0; } enum IntersectionType { kNone_IntersectionType, kIn_IntersectionType, kOut_IntersectionType }; static 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((SkScalarMul(pre, x3 - x4) - SkScalarMul(x1 - x2, post) / d, (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; } 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 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 target->drawState()->setDefaultVertexAttribs(); GrDrawTarget::AutoReleaseGeometry arg(target, 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.5f); 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 = 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: SkDEBUGFAIL("Curves not supported!"); default: // Unhandled cases SkASSERT(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: SkDEBUGFAIL("Round caps not supported!"); default: // No cap break; } } SkASSERT(vCount <= maxVertexCount); SkASSERT(iCount <= maxIndexCount); if (vCount > 0) { target->drawIndexed(kTriangles_GrPrimitiveType, 0, // start vertex 0, // start index vCount, iCount); } return true; }