/** * Draw a single path element of the clip stack into the accumulation bitmap */ void GrSWMaskHelper::draw(const SkPath& path, const SkStrokeRec& stroke, SkRegion::Op op, bool antiAlias, uint8_t alpha) { SkPaint paint; if (stroke.isHairlineStyle()) { paint.setStyle(SkPaint::kStroke_Style); paint.setStrokeWidth(SK_Scalar1); } else { if (stroke.isFillStyle()) { paint.setStyle(SkPaint::kFill_Style); } else { paint.setStyle(SkPaint::kStroke_Style); paint.setStrokeJoin(stroke.getJoin()); paint.setStrokeCap(stroke.getCap()); paint.setStrokeWidth(stroke.getWidth()); } } paint.setAntiAlias(antiAlias); if (SkRegion::kReplace_Op == op && 0xFF == alpha) { SkASSERT(0xFF == paint.getAlpha()); fDraw.drawPathCoverage(path, paint); } else { paint.setXfermodeMode(op_to_mode(op)); paint.setColor(SkColorSetARGB(alpha, alpha, alpha, alpha)); fDraw.drawPath(path, paint); } }
/** * Draw a single path element of the clip stack into the accumulation bitmap */ void GrSWMaskHelper::draw(const SkPath& path, const SkStrokeRec& stroke, SkRegion::Op op, bool antiAlias, uint8_t alpha) { SkPaint paint; if (stroke.isHairlineStyle()) { paint.setStyle(SkPaint::kStroke_Style); paint.setStrokeWidth(SK_Scalar1); } else { if (stroke.isFillStyle()) { paint.setStyle(SkPaint::kFill_Style); } else { paint.setStyle(SkPaint::kStroke_Style); paint.setStrokeJoin(stroke.getJoin()); paint.setStrokeCap(stroke.getCap()); paint.setStrokeWidth(stroke.getWidth()); } } SkXfermode* mode = SkXfermode::Create(op_to_mode(op)); paint.setXfermode(mode); paint.setAntiAlias(antiAlias); paint.setColor(SkColorSetARGB(alpha, alpha, alpha, alpha)); fDraw.drawPath(path, paint); SkSafeUnref(mode); }
uint64_t GrPath::ComputeStrokeKey(const SkStrokeRec& stroke) { enum { kStyleBits = 2, kJoinBits = 2, kCapBits = 2, kWidthBits = 29, kMiterBits = 29, kJoinShift = kStyleBits, kCapShift = kJoinShift + kJoinBits, kWidthShift = kCapShift + kCapBits, kMiterShift = kWidthShift + kWidthBits, kBitCount = kMiterShift + kMiterBits }; SK_COMPILE_ASSERT(SkStrokeRec::kStyleCount <= (1 << kStyleBits), style_shift_will_be_wrong); SK_COMPILE_ASSERT(SkPaint::kJoinCount <= (1 << kJoinBits), cap_shift_will_be_wrong); SK_COMPILE_ASSERT(SkPaint::kCapCount <= (1 << kCapBits), miter_shift_will_be_wrong); SK_COMPILE_ASSERT(kBitCount == 64, wrong_stroke_key_size); if (!stroke.needToApply()) { return SkStrokeRec::kFill_Style; } uint64_t key = stroke.getStyle(); key |= stroke.getJoin() << kJoinShift; key |= stroke.getCap() << kCapShift; key |= get_top_n_float_bits<kWidthBits>(stroke.getWidth()) << kWidthShift; key |= get_top_n_float_bits<kMiterBits>(stroke.getMiter()) << kMiterShift; return key; }
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)); }
/** * Draw a single path element of the clip stack into the accumulation bitmap */ void GrSWMaskHelper::draw(const SkPath& path, const SkStrokeRec& stroke, SkRegion::Op op, bool antiAlias, uint8_t alpha) { SkPaint paint; if (stroke.isHairlineStyle()) { paint.setStyle(SkPaint::kStroke_Style); paint.setStrokeWidth(SK_Scalar1); } else { if (stroke.isFillStyle()) { paint.setStyle(SkPaint::kFill_Style); } else { paint.setStyle(SkPaint::kStroke_Style); paint.setStrokeJoin(stroke.getJoin()); paint.setStrokeCap(stroke.getCap()); paint.setStrokeWidth(stroke.getWidth()); } } paint.setAntiAlias(antiAlias); SkTBlitterAllocator allocator; SkBlitter* blitter = nullptr; if (kBlitter_CompressionMode == fCompressionMode) { SkASSERT(fCompressedBuffer.get()); blitter = SkTextureCompressor::CreateBlitterForFormat( fPixels.width(), fPixels.height(), fCompressedBuffer.get(), &allocator, fCompressedFormat); } if (SkRegion::kReplace_Op == op && 0xFF == alpha) { SkASSERT(0xFF == paint.getAlpha()); fDraw.drawPathCoverage(path, paint, blitter); } else { paint.setXfermodeMode(op_to_mode(op)); paint.setColor(SkColorSetARGB(alpha, alpha, alpha, alpha)); fDraw.drawPath(path, paint, blitter); } }
// Currently asPoints is more restrictive then it needs to be. In the future // we need to: // allow kRound_Cap capping (could allow rotations in the matrix with this) // allow paths to be returned bool SkDashPathEffect::asPoints(PointData* results, const SkPath& src, const SkStrokeRec& rec, const SkMatrix& matrix, const SkRect* cullRect) const { // width < 0 -> fill && width == 0 -> hairline so requiring width > 0 rules both out if (fInitialDashLength < 0 || 0 >= rec.getWidth()) { return false; } // TODO: this next test could be eased up. We could allow any number of // intervals as long as all the ons match and all the offs match. // Additionally, they do not necessarily need to be integers. // We cannot allow arbitrary intervals since we want the returned points // to be uniformly sized. if (fCount != 2 || !SkScalarNearlyEqual(fIntervals[0], fIntervals[1]) || !SkScalarIsInt(fIntervals[0]) || !SkScalarIsInt(fIntervals[1])) { return false; } SkPoint pts[2]; if (!src.isLine(pts)) { return false; } // TODO: this test could be eased up to allow circles if (SkPaint::kButt_Cap != rec.getCap()) { return false; } // TODO: this test could be eased up for circles. Rotations could be allowed. if (!matrix.rectStaysRect()) { return false; } // See if the line can be limited to something plausible. if (!cull_line(pts, rec, matrix, cullRect, fIntervalLength)) { return false; } SkScalar length = SkPoint::Distance(pts[1], pts[0]); SkVector tangent = pts[1] - pts[0]; if (tangent.isZero()) { return false; } tangent.scale(SkScalarInvert(length)); // TODO: make this test for horizontal & vertical lines more robust bool isXAxis = true; if (SkScalarNearlyEqual(SK_Scalar1, tangent.fX) || SkScalarNearlyEqual(-SK_Scalar1, tangent.fX)) { results->fSize.set(SkScalarHalf(fIntervals[0]), SkScalarHalf(rec.getWidth())); } else if (SkScalarNearlyEqual(SK_Scalar1, tangent.fY) || SkScalarNearlyEqual(-SK_Scalar1, tangent.fY)) { results->fSize.set(SkScalarHalf(rec.getWidth()), SkScalarHalf(fIntervals[0])); isXAxis = false; } else if (SkPaint::kRound_Cap != rec.getCap()) { // Angled lines don't have axis-aligned boxes. return false; } if (results) { results->fFlags = 0; SkScalar clampedInitialDashLength = SkMinScalar(length, fInitialDashLength); if (SkPaint::kRound_Cap == rec.getCap()) { results->fFlags |= PointData::kCircles_PointFlag; } results->fNumPoints = 0; SkScalar len2 = length; if (clampedInitialDashLength > 0 || 0 == fInitialDashIndex) { SkASSERT(len2 >= clampedInitialDashLength); if (0 == fInitialDashIndex) { if (clampedInitialDashLength > 0) { if (clampedInitialDashLength >= fIntervals[0]) { ++results->fNumPoints; // partial first dash } len2 -= clampedInitialDashLength; } len2 -= fIntervals[1]; // also skip first space if (len2 < 0) { len2 = 0; } } else { len2 -= clampedInitialDashLength; // skip initial partial empty } } int numMidPoints = SkScalarFloorToInt(len2 / fIntervalLength); results->fNumPoints += numMidPoints; len2 -= numMidPoints * fIntervalLength; bool partialLast = false; if (len2 > 0) { if (len2 < fIntervals[0]) { partialLast = true; } else { ++numMidPoints; ++results->fNumPoints; } } results->fPoints = new SkPoint[results->fNumPoints]; SkScalar distance = 0; int curPt = 0; if (clampedInitialDashLength > 0 || 0 == fInitialDashIndex) { SkASSERT(clampedInitialDashLength <= length); if (0 == fInitialDashIndex) { if (clampedInitialDashLength > 0) { // partial first block SkASSERT(SkPaint::kRound_Cap != rec.getCap()); // can't handle partial circles SkScalar x = pts[0].fX + SkScalarMul(tangent.fX, SkScalarHalf(clampedInitialDashLength)); SkScalar y = pts[0].fY + SkScalarMul(tangent.fY, SkScalarHalf(clampedInitialDashLength)); SkScalar halfWidth, halfHeight; if (isXAxis) { halfWidth = SkScalarHalf(clampedInitialDashLength); halfHeight = SkScalarHalf(rec.getWidth()); } else { halfWidth = SkScalarHalf(rec.getWidth()); halfHeight = SkScalarHalf(clampedInitialDashLength); } if (clampedInitialDashLength < fIntervals[0]) { // This one will not be like the others results->fFirst.addRect(x - halfWidth, y - halfHeight, x + halfWidth, y + halfHeight); } else { SkASSERT(curPt < results->fNumPoints); results->fPoints[curPt].set(x, y); ++curPt; } distance += clampedInitialDashLength; } distance += fIntervals[1]; // skip over the next blank block too } else { distance += clampedInitialDashLength; } } if (0 != numMidPoints) { distance += SkScalarHalf(fIntervals[0]); for (int i = 0; i < numMidPoints; ++i) { SkScalar x = pts[0].fX + SkScalarMul(tangent.fX, distance); SkScalar y = pts[0].fY + SkScalarMul(tangent.fY, distance); SkASSERT(curPt < results->fNumPoints); results->fPoints[curPt].set(x, y); ++curPt; distance += fIntervalLength; } distance -= SkScalarHalf(fIntervals[0]); } if (partialLast) { // partial final block SkASSERT(SkPaint::kRound_Cap != rec.getCap()); // can't handle partial circles SkScalar temp = length - distance; SkASSERT(temp < fIntervals[0]); SkScalar x = pts[0].fX + SkScalarMul(tangent.fX, distance + SkScalarHalf(temp)); SkScalar y = pts[0].fY + SkScalarMul(tangent.fY, distance + SkScalarHalf(temp)); SkScalar halfWidth, halfHeight; if (isXAxis) { halfWidth = SkScalarHalf(temp); halfHeight = SkScalarHalf(rec.getWidth()); } else { halfWidth = SkScalarHalf(rec.getWidth()); halfHeight = SkScalarHalf(temp); } results->fLast.addRect(x - halfWidth, y - halfHeight, x + halfWidth, y + halfHeight); } SkASSERT(curPt == results->fNumPoints); } return true; }
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 = SkScalarDiv(SkScalarMul(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; }