/*
* 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;
}