virtual void handlePath(SkCanvas* canvas, const SkPath& path, const SkPaint& paint, int N) override { SkPoint pts[2]; if (!path.isLine(pts) || pts[0].fY != pts[1].fY) { this->INHERITED::handlePath(canvas, path, paint, N); } else { SkRect rect; rect.fLeft = pts[0].fX; rect.fTop = pts[0].fY - paint.getStrokeWidth() / 2; rect.fRight = rect.fLeft + SkIntToScalar(fWidth); rect.fBottom = rect.fTop + paint.getStrokeWidth(); SkPaint p(paint); p.setStyle(SkPaint::kFill_Style); p.setPathEffect(nullptr); int count = SkScalarRoundToInt((pts[1].fX - pts[0].fX) / (2*fWidth)); SkScalar dx = SkIntToScalar(2 * fWidth); for (int i = 0; i < N*10; ++i) { SkRect r = rect; for (int j = 0; j < count; ++j) { canvas->drawRect(r, p); r.offset(dx, 0); } } } }
// Only handles lines for now. If returns true, dstPath is the new (smaller) // path. If returns false, then dstPath parameter is ignored. static bool cull_path(const SkPath& srcPath, const SkStrokeRec& rec, const SkRect* cullRect, SkScalar intervalLength, SkPath* dstPath) { if (nullptr == cullRect) { return false; } SkPoint pts[2]; if (!srcPath.isLine(pts)) { return false; } SkRect bounds = *cullRect; outset_for_stroke(&bounds, rec); SkScalar dx = pts[1].x() - pts[0].x(); SkScalar dy = pts[1].y() - pts[0].y(); // just do horizontal lines for now (lazy) if (dy) { return false; } SkScalar minX = pts[0].fX; SkScalar maxX = pts[1].fX; if (dx < 0) { SkTSwap(minX, maxX); } SkASSERT(minX <= maxX); if (maxX < bounds.fLeft || minX > bounds.fRight) { return false; } // Now we actually perform the chop, removing the excess to the left and // right of the bounds (keeping our new line "in phase" with the dash, // hence the (mod intervalLength). if (minX < bounds.fLeft) { minX = bounds.fLeft - SkScalarMod(bounds.fLeft - minX, intervalLength); } if (maxX > bounds.fRight) { maxX = bounds.fRight + SkScalarMod(maxX - bounds.fRight, intervalLength); } SkASSERT(maxX >= minX); if (dx < 0) { SkTSwap(minX, maxX); } pts[0].fX = minX; pts[1].fX = maxX; dstPath->moveTo(pts[0]); dstPath->lineTo(pts[1]); return true; }
bool init(const SkPath& src, SkPath* dst, SkStrokeRec* rec, int intervalCount, SkScalar intervalLength) { if (rec->isHairlineStyle() || !src.isLine(fPts)) { return false; } // can relax this in the future, if we handle square and round caps if (SkPaint::kButt_Cap != rec->getCap()) { return false; } SkScalar pathLength = SkPoint::Distance(fPts[0], fPts[1]); fTangent = fPts[1] - fPts[0]; if (fTangent.isZero()) { return false; } fPathLength = pathLength; fTangent.scale(SkScalarInvert(pathLength)); fTangent.rotateCCW(&fNormal); fNormal.scale(SkScalarHalf(rec->getWidth())); // now estimate how many quads will be added to the path // resulting segments = pathLen * intervalCount / intervalLen // resulting points = 4 * segments SkScalar ptCount = SkScalarMulDiv(pathLength, SkIntToScalar(intervalCount), intervalLength); ptCount = SkTMin(ptCount, SkDashPath::kMaxDashCount); int n = SkScalarCeilToInt(ptCount) << 2; dst->incReserve(n); // we will take care of the stroking rec->setFillStyle(); return true; }
// 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; }