DEF_TEST(PathMeasure, reporter) { SkPath path; path.moveTo(0, 0); path.lineTo(SK_Scalar1, 0); path.lineTo(SK_Scalar1, SK_Scalar1); path.lineTo(0, SK_Scalar1); SkPathMeasure meas(path, true); SkScalar length = meas.getLength(); SkASSERT(length == SK_Scalar1*4); path.reset(); path.moveTo(0, 0); path.lineTo(SK_Scalar1*3, SK_Scalar1*4); meas.setPath(&path, false); length = meas.getLength(); REPORTER_ASSERT(reporter, length == SK_Scalar1*5); path.reset(); path.addCircle(0, 0, SK_Scalar1); meas.setPath(&path, true); length = meas.getLength(); // SkDebugf("circle arc-length = %g\n", length); // Test the behavior following a close not followed by a move. path.reset(); path.lineTo(SK_Scalar1, 0); path.lineTo(SK_Scalar1, SK_Scalar1); path.lineTo(0, SK_Scalar1); path.close(); path.lineTo(-SK_Scalar1, 0); meas.setPath(&path, false); length = meas.getLength(); REPORTER_ASSERT(reporter, length == SK_Scalar1 * 4); meas.nextContour(); length = meas.getLength(); REPORTER_ASSERT(reporter, length == SK_Scalar1); SkPoint position; SkVector tangent; REPORTER_ASSERT(reporter, meas.getPosTan(SK_ScalarHalf, &position, &tangent)); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(position.fX, -SK_ScalarHalf, 0.0001f)); REPORTER_ASSERT(reporter, position.fY == 0); REPORTER_ASSERT(reporter, tangent.fX == -SK_Scalar1); REPORTER_ASSERT(reporter, tangent.fY == 0); // Test degenerate paths path.reset(); path.moveTo(0, 0); path.lineTo(0, 0); path.lineTo(SK_Scalar1, 0); path.quadTo(SK_Scalar1, 0, SK_Scalar1, 0); path.quadTo(SK_Scalar1, SK_Scalar1, SK_Scalar1, SK_Scalar1 * 2); path.cubicTo(SK_Scalar1, SK_Scalar1 * 2, SK_Scalar1, SK_Scalar1 * 2, SK_Scalar1, SK_Scalar1 * 2); path.cubicTo(SK_Scalar1*2, SK_Scalar1 * 2, SK_Scalar1*3, SK_Scalar1 * 2, SK_Scalar1*4, SK_Scalar1 * 2); meas.setPath(&path, false); length = meas.getLength(); REPORTER_ASSERT(reporter, length == SK_Scalar1 * 6); REPORTER_ASSERT(reporter, meas.getPosTan(SK_ScalarHalf, &position, &tangent)); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(position.fX, SK_ScalarHalf, 0.0001f)); REPORTER_ASSERT(reporter, position.fY == 0); REPORTER_ASSERT(reporter, tangent.fX == SK_Scalar1); REPORTER_ASSERT(reporter, tangent.fY == 0); REPORTER_ASSERT(reporter, meas.getPosTan(2.5f, &position, &tangent)); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(position.fX, SK_Scalar1, 0.0001f)); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(position.fY, 1.5f)); REPORTER_ASSERT(reporter, tangent.fX == 0); REPORTER_ASSERT(reporter, tangent.fY == SK_Scalar1); REPORTER_ASSERT(reporter, meas.getPosTan(4.5f, &position, &tangent)); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(position.fX, 2.5f, 0.0001f)); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(position.fY, 2.0f, 0.0001f)); REPORTER_ASSERT(reporter, tangent.fX == SK_Scalar1); REPORTER_ASSERT(reporter, tangent.fY == 0); path.reset(); path.moveTo(0, 0); path.lineTo(SK_Scalar1, 0); path.moveTo(SK_Scalar1, SK_Scalar1); path.moveTo(SK_Scalar1 * 2, SK_Scalar1 * 2); path.lineTo(SK_Scalar1, SK_Scalar1 * 2); meas.setPath(&path, false); length = meas.getLength(); REPORTER_ASSERT(reporter, length == SK_Scalar1); REPORTER_ASSERT(reporter, meas.getPosTan(SK_ScalarHalf, &position, &tangent)); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(position.fX, SK_ScalarHalf, 0.0001f)); REPORTER_ASSERT(reporter, position.fY == 0); REPORTER_ASSERT(reporter, tangent.fX == SK_Scalar1); REPORTER_ASSERT(reporter, tangent.fY == 0); meas.nextContour(); length = meas.getLength(); REPORTER_ASSERT(reporter, length == SK_Scalar1); REPORTER_ASSERT(reporter, meas.getPosTan(SK_ScalarHalf, &position, &tangent)); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(position.fX, 1.5f, 0.0001f)); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(position.fY, 2.0f, 0.0001f)); REPORTER_ASSERT(reporter, tangent.fX == -SK_Scalar1); REPORTER_ASSERT(reporter, tangent.fY == 0); test_small_segment(); test_small_segment2(); test_small_segment3(); }
void SkPathStroker::line_to(const SkPoint& currPt, const SkVector& normal) { fOuter.lineTo(currPt.fX + normal.fX, currPt.fY + normal.fY); fInner.lineTo(currPt.fX - normal.fX, currPt.fY - normal.fY); }
void SkPathStroker::cubicTo(const SkPoint& pt1, const SkPoint& pt2, const SkPoint& pt3) { bool degenerateAB = SkPath::IsLineDegenerate(fPrevPt, pt1); bool degenerateBC = SkPath::IsLineDegenerate(pt1, pt2); bool degenerateCD = SkPath::IsLineDegenerate(pt2, pt3); if (degenerateAB + degenerateBC + degenerateCD >= 2) { this->lineTo(pt3); return; } SkVector normalAB, unitAB, normalCD, unitCD; // find the first tangent (which might be pt1 or pt2 { const SkPoint* nextPt = &pt1; if (degenerateAB) nextPt = &pt2; this->preJoinTo(*nextPt, &normalAB, &unitAB, false); } { SkPoint pts[4], tmp[13]; int i, count; SkVector n, u; SkScalar tValues[3]; pts[0] = fPrevPt; pts[1] = pt1; pts[2] = pt2; pts[3] = pt3; #if 1 count = SkChopCubicAtMaxCurvature(pts, tmp, tValues); #else count = 1; memcpy(tmp, pts, 4 * sizeof(SkPoint)); #endif n = normalAB; u = unitAB; for (i = 0; i < count; i++) { this->cubic_to(&tmp[i * 3], n, u, &normalCD, &unitCD, kMaxCubicSubdivide); if (i == count - 1) { break; } n = normalCD; u = unitCD; } // check for too pinchy for (i = 1; i < count; i++) { SkPoint p; SkVector v, c; SkEvalCubicAt(pts, tValues[i - 1], &p, &v, &c); SkScalar dot = SkPoint::DotProduct(c, c); v.scale(SkScalarInvert(dot)); if (SkScalarNearlyZero(v.fX) && SkScalarNearlyZero(v.fY)) { fExtra.addCircle(p.fX, p.fY, fRadius, SkPath::kCW_Direction); } } } this->postJoinTo(pt3, normalCD, unitCD); }
static SkPath testPath() { SkPath path; path.addRect(SkRect::MakeXYWH(SkIntToScalar(0), SkIntToScalar(0), SkIntToScalar(2), SkIntToScalar(1))); return path; }
void SkScalerContext::internalGetPath(const SkGlyph& glyph, SkPath* fillPath, SkPath* devPath, SkMatrix* fillToDevMatrix) { SkPath path; generatePath(glyph, &path); if (fRec.fFlags & SkScalerContext::kSubpixelPositioning_Flag) { SkFixed dx = glyph.getSubXFixed(); SkFixed dy = glyph.getSubYFixed(); if (dx | dy) { path.offset(SkFixedToScalar(dx), SkFixedToScalar(dy)); } } if (fRec.fFrameWidth > 0 || fPathEffect != NULL) { // need the path in user-space, with only the point-size applied // so that our stroking and effects will operate the same way they // would if the user had extracted the path themself, and then // called drawPath SkPath localPath; SkMatrix matrix, inverse; fRec.getMatrixFrom2x2(&matrix); if (!matrix.invert(&inverse)) { // assume fillPath and devPath are already empty. return; } path.transform(inverse, &localPath); // now localPath is only affected by the paint settings, and not the canvas matrix SkStrokeRec rec(SkStrokeRec::kFill_InitStyle); if (fRec.fFrameWidth > 0) { rec.setStrokeStyle(fRec.fFrameWidth, SkToBool(fRec.fFlags & kFrameAndFill_Flag)); // glyphs are always closed contours, so cap type is ignored, // so we just pass something. rec.setStrokeParams(SkPaint::kButt_Cap, (SkPaint::Join)fRec.fStrokeJoin, fRec.fMiterLimit); } if (fPathEffect) { SkPath effectPath; if (fPathEffect->filterPath(&effectPath, localPath, &rec, NULL)) { localPath.swap(effectPath); } } if (rec.needToApply()) { SkPath strokePath; if (rec.applyToPath(&strokePath, localPath)) { localPath.swap(strokePath); } } // now return stuff to the caller if (fillToDevMatrix) { *fillToDevMatrix = matrix; } if (devPath) { localPath.transform(matrix, devPath); } if (fillPath) { fillPath->swap(localPath); } } else { // nothing tricky to do if (fillToDevMatrix) { fillToDevMatrix->reset(); } if (devPath) { if (fillPath == NULL) { devPath->swap(path); } else { *devPath = path; } } if (fillPath) { fillPath->swap(path); } } if (devPath) { devPath->updateBoundsCache(); } if (fillPath) { fillPath->updateBoundsCache(); } }
void GrGLPath::InitPathObject(GrGLGpu* gpu, GrGLuint pathID, const SkPath& skPath, const GrStrokeInfo& stroke) { SkASSERT(!stroke.isDashed()); if (!skPath.isEmpty()) { int verbCnt = skPath.countVerbs(); int pointCnt = skPath.countPoints(); int minCoordCnt = pointCnt * 2; SkSTArray<16, GrGLubyte, true> pathCommands(verbCnt); SkSTArray<16, GrGLfloat, true> pathCoords(minCoordCnt); SkDEBUGCODE(int numCoords = 0); if ((skPath.getSegmentMasks() & SkPath::kConic_SegmentMask) == 0) { // This branch does type punning, converting SkPoint* to GrGLfloat*. SK_COMPILE_ASSERT(sizeof(SkPoint) == sizeof(GrGLfloat) * 2, sk_point_not_two_floats); // This branch does not convert with SkScalarToFloat. #ifndef SK_SCALAR_IS_FLOAT #error Need SK_SCALAR_IS_FLOAT. #endif pathCommands.resize_back(verbCnt); pathCoords.resize_back(minCoordCnt); skPath.getPoints(reinterpret_cast<SkPoint*>(&pathCoords[0]), pointCnt); skPath.getVerbs(&pathCommands[0], verbCnt); for (int i = 0; i < verbCnt; ++i) { SkPath::Verb v = static_cast<SkPath::Verb>(pathCommands[i]); pathCommands[i] = verb_to_gl_path_cmd(v); SkDEBUGCODE(numCoords += num_coords(v)); } } else { SkPoint points[4]; SkPath::RawIter iter(skPath); SkPath::Verb verb; while ((verb = iter.next(points)) != SkPath::kDone_Verb) { pathCommands.push_back(verb_to_gl_path_cmd(verb)); GrGLfloat coords[6]; int coordsForVerb; switch (verb) { case SkPath::kMove_Verb: points_to_coords(points, 0, 1, coords); coordsForVerb = 2; break; case SkPath::kLine_Verb: points_to_coords(points, 1, 1, coords); coordsForVerb = 2; break; case SkPath::kConic_Verb: points_to_coords(points, 1, 2, coords); coords[4] = SkScalarToFloat(iter.conicWeight()); coordsForVerb = 5; break; case SkPath::kQuad_Verb: points_to_coords(points, 1, 2, coords); coordsForVerb = 4; break; case SkPath::kCubic_Verb: points_to_coords(points, 1, 3, coords); coordsForVerb = 6; break; case SkPath::kClose_Verb: continue; default: SkASSERT(false); // Not reached. continue; } SkDEBUGCODE(numCoords += num_coords(verb)); pathCoords.push_back_n(coordsForVerb, coords); } } SkASSERT(verbCnt == pathCommands.count()); SkASSERT(numCoords == pathCoords.count()); GR_GL_CALL(gpu->glInterface(), PathCommands(pathID, pathCommands.count(), &pathCommands[0], pathCoords.count(), GR_GL_FLOAT, &pathCoords[0])); } else { GR_GL_CALL(gpu->glInterface(), PathCommands(pathID, 0, NULL, 0, GR_GL_FLOAT, NULL)); } if (stroke.needToApply()) { SkASSERT(!stroke.isHairlineStyle()); GR_GL_CALL(gpu->glInterface(), PathParameterf(pathID, GR_GL_PATH_STROKE_WIDTH, SkScalarToFloat(stroke.getWidth()))); GR_GL_CALL(gpu->glInterface(), PathParameterf(pathID, GR_GL_PATH_MITER_LIMIT, SkScalarToFloat(stroke.getMiter()))); GrGLenum join = join_to_gl_join(stroke.getJoin()); GR_GL_CALL(gpu->glInterface(), PathParameteri(pathID, GR_GL_PATH_JOIN_STYLE, join)); GrGLenum cap = cap_to_gl_cap(stroke.getCap()); GR_GL_CALL(gpu->glInterface(), PathParameteri(pathID, GR_GL_PATH_END_CAPS, cap)); GR_GL_CALL(gpu->glInterface(), PathParameterf(pathID, GR_GL_PATH_STROKE_BOUND, 0.02f)); } }
static bool get_geometry(const SkPath& path, const SkMatrix& m, PLSVertices& triVertices, PLSVertices& quadVertices, GrResourceProvider* resourceProvider, SkRect bounds) { SkScalar screenSpaceTol = GrPathUtils::kDefaultTolerance; SkScalar tol = GrPathUtils::scaleToleranceToSrc(screenSpaceTol, m, bounds); int contourCnt; int maxPts = GrPathUtils::worstCasePointCount(path, &contourCnt, tol); if (maxPts <= 0) { return 0; } SkPath linesOnlyPath; linesOnlyPath.setFillType(path.getFillType()); SkSTArray<15, SkPoint, true> quadPoints; SkPath::Iter iter(path, true); bool done = false; while (!done) { SkPoint pts[4]; SkPath::Verb verb = iter.next(pts); switch (verb) { case SkPath::kMove_Verb: SkASSERT(quadPoints.count() % 3 == 0); for (int i = 0; i < quadPoints.count(); i += 3) { add_quad(&quadPoints[i], quadVertices); } quadPoints.reset(); m.mapPoints(&pts[0], 1); linesOnlyPath.moveTo(pts[0]); break; case SkPath::kLine_Verb: m.mapPoints(&pts[1], 1); linesOnlyPath.lineTo(pts[1]); break; case SkPath::kQuad_Verb: m.mapPoints(pts, 3); linesOnlyPath.lineTo(pts[2]); quadPoints.push_back(pts[0]); quadPoints.push_back(pts[1]); quadPoints.push_back(pts[2]); break; case SkPath::kCubic_Verb: { m.mapPoints(pts, 4); SkSTArray<15, SkPoint, true> quads; GrPathUtils::convertCubicToQuads(pts, kCubicTolerance, &quads); int count = quads.count(); for (int q = 0; q < count; q += 3) { linesOnlyPath.lineTo(quads[q + 2]); quadPoints.push_back(quads[q]); quadPoints.push_back(quads[q + 1]); quadPoints.push_back(quads[q + 2]); } break; } case SkPath::kConic_Verb: { m.mapPoints(pts, 3); SkScalar weight = iter.conicWeight(); SkAutoConicToQuads converter; const SkPoint* quads = converter.computeQuads(pts, weight, kConicTolerance); int count = converter.countQuads(); for (int i = 0; i < count; ++i) { linesOnlyPath.lineTo(quads[2 * i + 2]); quadPoints.push_back(quads[2 * i]); quadPoints.push_back(quads[2 * i + 1]); quadPoints.push_back(quads[2 * i + 2]); } break; } case SkPath::kClose_Verb: linesOnlyPath.close(); break; case SkPath::kDone_Verb: done = true; break; default: SkASSERT(false); } } SkASSERT(quadPoints.count() % 3 == 0); for (int i = 0; i < quadPoints.count(); i += 3) { add_quad(&quadPoints[i], quadVertices); } static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain(); GrUniqueKey key; GrUniqueKey::Builder builder(&key, kDomain, 2); builder[0] = path.getGenerationID(); builder[1] = path.getFillType(); builder.finish(); GrTessellator::WindingVertex* windingVertices; int triVertexCount = GrTessellator::PathToVertices(linesOnlyPath, 0, bounds, &windingVertices); if (triVertexCount > 0) { for (int i = 0; i < triVertexCount; i += 3) { SkPoint p1 = windingVertices[i].fPos; SkPoint p2 = windingVertices[i + 1].fPos; SkPoint p3 = windingVertices[i + 2].fPos; int winding = windingVertices[i].fWinding; SkASSERT(windingVertices[i + 1].fWinding == winding); SkASSERT(windingVertices[i + 2].fWinding == winding); SkScalar cross = (p2 - p1).cross(p3 - p1); SkPoint bloated[3] = { p1, p2, p3 }; if (cross < 0.0f) { SkTSwap(p1, p3); } if (bloat_tri(bloated)) { triVertices.push_back({ bloated[0], p1, p2, p3, winding }); triVertices.push_back({ bloated[1], p1, p2, p3, winding }); triVertices.push_back({ bloated[2], p1, p2, p3, winding }); } else { SkScalar minX = SkTMin(p1.fX, SkTMin(p2.fX, p3.fX)) - 1.0f; SkScalar minY = SkTMin(p1.fY, SkTMin(p2.fY, p3.fY)) - 1.0f; SkScalar maxX = SkTMax(p1.fX, SkTMax(p2.fX, p3.fX)) + 1.0f; SkScalar maxY = SkTMax(p1.fY, SkTMax(p2.fY, p3.fY)) + 1.0f; triVertices.push_back({ { minX, minY }, p1, p2, p3, winding }); triVertices.push_back({ { maxX, minY }, p1, p2, p3, winding }); triVertices.push_back({ { minX, maxY }, p1, p2, p3, winding }); triVertices.push_back({ { maxX, minY }, p1, p2, p3, winding }); triVertices.push_back({ { maxX, maxY }, p1, p2, p3, winding }); triVertices.push_back({ { minX, maxY }, p1, p2, p3, winding }); } } delete[] windingVertices; } return triVertexCount > 0 || quadVertices.count() > 0; }
void SkScalerContext::internalGetPath(const SkGlyph& glyph, SkPath* fillPath, SkPath* devPath, SkMatrix* fillToDevMatrix) { SkPath path; this->getGlyphContext(glyph)->generatePath(glyph, &path); if (fRec.fFrameWidth > 0 || fPathEffect != NULL) { // need the path in user-space, with only the point-size applied // so that our stroking and effects will operate the same way they // would if the user had extracted the path themself, and then // called drawPath SkPath localPath; SkMatrix matrix, inverse; fRec.getMatrixFrom2x2(&matrix); matrix.invert(&inverse); path.transform(inverse, &localPath); // now localPath is only affected by the paint settings, and not the canvas matrix SkScalar width = fRec.fFrameWidth; if (fPathEffect) { SkPath effectPath; if (fPathEffect->filterPath(&effectPath, localPath, &width)) localPath.swap(effectPath); } if (width > 0) { SkStroke stroker; SkPath outline; stroker.setWidth(width); stroker.setMiterLimit(fRec.fMiterLimit); stroker.setJoin((SkPaint::Join)fRec.fStrokeJoin); stroker.setDoFill(SkToBool(fRec.fFlags & kFrameAndFill_Flag)); stroker.strokePath(localPath, &outline); localPath.swap(outline); } // now return stuff to the caller if (fillToDevMatrix) *fillToDevMatrix = matrix; if (devPath) localPath.transform(matrix, devPath); if (fillPath) fillPath->swap(localPath); } else // nothing tricky to do { if (fillToDevMatrix) fillToDevMatrix->reset(); if (devPath) { if (fillPath == NULL) devPath->swap(path); else *devPath = path; } if (fillPath) fillPath->swap(path); } if (devPath) devPath->updateBoundsCache(); if (fillPath) fillPath->updateBoundsCache(); }
// 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 SkDashImpl::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 (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 } } // Too many midpoints can cause results->fNumPoints to overflow or // otherwise cause the results->fPoints allocation below to OOM. // Cap it to a sane value. SkScalar numIntervals = len2 / fIntervalLength; if (!SkScalarIsFinite(numIntervals) || numIntervals > SkDashPath::kMaxDashCount) { return false; } int numMidPoints = SkScalarFloorToInt(numIntervals); 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 + tangent.fX * SkScalarHalf(clampedInitialDashLength); SkScalar y = pts[0].fY + 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 + tangent.fX * distance; SkScalar y = pts[0].fY + 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 + tangent.fX * (distance + SkScalarHalf(temp)); SkScalar y = pts[0].fY + 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; }
void GrCCFiller::PathInfo::tessellateFan(const GrCCFillGeometry& geometry, int verbsIdx, int ptsIdx, const SkIRect& clippedDevIBounds, PrimitiveTallies* newTriangleCounts) { using Verb = GrCCFillGeometry::Verb; SkASSERT(-1 == fFanTessellationCount); SkASSERT(!fFanTessellation); const SkTArray<Verb, true>& verbs = geometry.verbs(); const SkTArray<SkPoint, true>& pts = geometry.points(); newTriangleCounts->fTriangles = newTriangleCounts->fWeightedTriangles = 0; // Build an SkPath of the Redbook fan. We use "winding" fill type right now because we are // producing a coverage count, and must fill in every region that has non-zero wind. The // path processor will convert coverage count to the appropriate fill type later. SkPath fan; fan.setFillType(SkPath::kWinding_FillType); SkASSERT(Verb::kBeginPath == verbs[verbsIdx]); for (int i = verbsIdx + 1; i < verbs.count(); ++i) { switch (verbs[i]) { case Verb::kBeginPath: SK_ABORT("Invalid GrCCFillGeometry"); continue; case Verb::kBeginContour: fan.moveTo(pts[ptsIdx++]); continue; case Verb::kLineTo: fan.lineTo(pts[ptsIdx++]); continue; case Verb::kMonotonicQuadraticTo: case Verb::kMonotonicConicTo: fan.lineTo(pts[ptsIdx + 1]); ptsIdx += 2; continue; case Verb::kMonotonicCubicTo: fan.lineTo(pts[ptsIdx + 2]); ptsIdx += 3; continue; case Verb::kEndClosedContour: case Verb::kEndOpenContour: fan.close(); continue; } } GrTessellator::WindingVertex* vertices = nullptr; fFanTessellationCount = GrTessellator::PathToVertices(fan, std::numeric_limits<float>::infinity(), SkRect::Make(clippedDevIBounds), &vertices); if (fFanTessellationCount <= 0) { SkASSERT(0 == fFanTessellationCount); SkASSERT(nullptr == vertices); return; } SkASSERT(0 == fFanTessellationCount % 3); for (int i = 0; i < fFanTessellationCount; i += 3) { int tessWinding = vertices[i].fWinding; SkASSERT(tessWinding == vertices[i + 1].fWinding); SkASSERT(tessWinding == vertices[i + 2].fWinding); // Ensure this triangle's points actually wind in the same direction as tessWinding. // CCPR shaders use the sign of wind to determine which direction to bloat, so even for // "wound" triangles the winding sign and point ordering need to agree. float ax = vertices[i].fPos.fX - vertices[i + 1].fPos.fX; float ay = vertices[i].fPos.fY - vertices[i + 1].fPos.fY; float bx = vertices[i].fPos.fX - vertices[i + 2].fPos.fX; float by = vertices[i].fPos.fY - vertices[i + 2].fPos.fY; float wind = ax*by - ay*bx; if ((wind > 0) != (-tessWinding > 0)) { // Tessellator has opposite winding sense. std::swap(vertices[i + 1].fPos, vertices[i + 2].fPos); } if (1 == abs(tessWinding)) { ++newTriangleCounts->fTriangles; } else { ++newTriangleCounts->fWeightedTriangles; } } fFanTessellation.reset(vertices); }
void GrCCFiller::parseDeviceSpaceFill(const SkPath& path, const SkPoint* deviceSpacePts, GrScissorTest scissorTest, const SkIRect& clippedDevIBounds, const SkIVector& devToAtlasOffset) { SkASSERT(!fInstanceBuffer); // Can't call after prepareToDraw(). SkASSERT(!path.isEmpty()); int currPathPointsIdx = fGeometry.points().count(); int currPathVerbsIdx = fGeometry.verbs().count(); PrimitiveTallies currPathPrimitiveCounts = PrimitiveTallies(); fGeometry.beginPath(); const float* conicWeights = SkPathPriv::ConicWeightData(path); int ptsIdx = 0; int conicWeightsIdx = 0; bool insideContour = false; for (SkPath::Verb verb : SkPathPriv::Verbs(path)) { switch (verb) { case SkPath::kMove_Verb: if (insideContour) { currPathPrimitiveCounts += fGeometry.endContour(); } fGeometry.beginContour(deviceSpacePts[ptsIdx]); ++ptsIdx; insideContour = true; continue; case SkPath::kClose_Verb: if (insideContour) { currPathPrimitiveCounts += fGeometry.endContour(); } insideContour = false; continue; case SkPath::kLine_Verb: fGeometry.lineTo(&deviceSpacePts[ptsIdx - 1]); ++ptsIdx; continue; case SkPath::kQuad_Verb: fGeometry.quadraticTo(&deviceSpacePts[ptsIdx - 1]); ptsIdx += 2; continue; case SkPath::kCubic_Verb: fGeometry.cubicTo(&deviceSpacePts[ptsIdx - 1]); ptsIdx += 3; continue; case SkPath::kConic_Verb: fGeometry.conicTo(&deviceSpacePts[ptsIdx - 1], conicWeights[conicWeightsIdx]); ptsIdx += 2; ++conicWeightsIdx; continue; default: SK_ABORT("Unexpected path verb."); } } SkASSERT(ptsIdx == path.countPoints()); SkASSERT(conicWeightsIdx == SkPathPriv::ConicWeightCnt(path)); if (insideContour) { currPathPrimitiveCounts += fGeometry.endContour(); } fPathInfos.emplace_back(scissorTest, devToAtlasOffset); // Tessellate fans from very large and/or simple paths, in order to reduce overdraw. int numVerbs = fGeometry.verbs().count() - currPathVerbsIdx - 1; int64_t tessellationWork = (int64_t)numVerbs * (32 - SkCLZ(numVerbs)); // N log N. int64_t fanningWork = (int64_t)clippedDevIBounds.height() * clippedDevIBounds.width(); if (tessellationWork * (50*50) + (100*100) < fanningWork) { // Don't tessellate under 100x100. fPathInfos.back().tessellateFan(fGeometry, currPathVerbsIdx, currPathPointsIdx, clippedDevIBounds, &currPathPrimitiveCounts); } fTotalPrimitiveCounts[(int)scissorTest] += currPathPrimitiveCounts; if (GrScissorTest::kEnabled == scissorTest) { fScissorSubBatches.push_back() = {fTotalPrimitiveCounts[(int)GrScissorTest::kEnabled], clippedDevIBounds.makeOffset(devToAtlasOffset.fX, devToAtlasOffset.fY)}; } }
SkPath create_concave_path(const SkPoint& offset) { SkPath concavePath; concavePath.moveTo(kMin, kMin); concavePath.lineTo(kMid, 105.0f); concavePath.lineTo(kMax, kMin); concavePath.lineTo(295.0f, kMid); concavePath.lineTo(kMax, kMax); concavePath.lineTo(kMid, 295.0f); concavePath.lineTo(kMin, kMax); concavePath.lineTo(105.0f, kMid); concavePath.close(); concavePath.offset(offset.fX, offset.fY); return concavePath; }
// A quad which becomes NaN when interpolated. static SkPath create_path_22() { SkPath path; path.moveTo(-5.71889e+13f, 1.36759e+09f); path.quadTo(2.45472e+19f, -3.12406e+15f, -2.19589e+18f, 2.79462e+14f); return path; }
// Exercises the case where an edge becomes collinear with *two* of its // adjacent neighbour edges after splitting. // This is a reduction from // http://mooooo.ooo/chebyshev-sine-approximation/horner_ulp.svg static SkPath create_path_19() { SkPath path; path.moveTo( 351.99298095703125, 348.23046875); path.lineTo( 351.91876220703125, 347.33984375); path.lineTo( 351.91876220703125, 346.1953125); path.lineTo( 351.90313720703125, 347.734375); path.lineTo( 351.90313720703125, 346.1328125); path.lineTo( 351.87579345703125, 347.93359375); path.lineTo( 351.87579345703125, 345.484375); path.lineTo( 351.86407470703125, 347.7890625); path.lineTo( 351.86407470703125, 346.2109375); path.lineTo( 351.84844970703125, 347.63763427734375); path.lineTo( 351.84454345703125, 344.19232177734375); path.lineTo( 351.78204345703125, 346.9483642578125); path.lineTo( 351.758636474609375, 347.18310546875); path.lineTo( 351.75469970703125, 346.75); path.lineTo( 351.75469970703125, 345.46875); path.lineTo( 352.5546875, 345.46875); path.lineTo( 352.55078125, 347.01953125); path.lineTo( 351.75079345703125, 347.02313232421875); path.lineTo( 351.74688720703125, 346.15203857421875); path.lineTo( 351.74688720703125, 347.646148681640625); path.lineTo( 352.5390625, 346.94140625); path.lineTo( 351.73907470703125, 346.94268798828125); path.lineTo( 351.73516845703125, 344.48565673828125); path.lineTo( 352.484375, 346.73828125); path.lineTo( 351.68438720703125, 346.7401123046875); path.lineTo( 352.4765625, 346.546875); path.lineTo( 351.67657470703125, 346.54937744140625); path.lineTo( 352.47265625, 346.75390625); path.lineTo( 351.67266845703125, 346.756622314453125); path.lineTo( 351.66876220703125, 345.612091064453125); return path; }
static void test_clip_bound_opt(skiatest::Reporter* reporter) { // Test for crbug.com/229011 SkRect rect1 = SkRect::MakeXYWH(SkIntToScalar(4), SkIntToScalar(4), SkIntToScalar(2), SkIntToScalar(2)); SkRect rect2 = SkRect::MakeXYWH(SkIntToScalar(7), SkIntToScalar(7), SkIntToScalar(1), SkIntToScalar(1)); SkRect rect3 = SkRect::MakeXYWH(SkIntToScalar(6), SkIntToScalar(6), SkIntToScalar(1), SkIntToScalar(1)); SkPath invPath; invPath.addOval(rect1); invPath.setFillType(SkPath::kInverseEvenOdd_FillType); SkPath path; path.addOval(rect2); SkPath path2; path2.addOval(rect3); SkIRect clipBounds; SkPictureRecorder recorder; // Testing conservative-raster-clip that is enabled by PictureRecord { SkCanvas* canvas = recorder.beginRecording(10, 10); canvas->clipPath(invPath, SkRegion::kIntersect_Op); bool nonEmpty = canvas->getClipDeviceBounds(&clipBounds); REPORTER_ASSERT(reporter, true == nonEmpty); REPORTER_ASSERT(reporter, 0 == clipBounds.fLeft); REPORTER_ASSERT(reporter, 0 == clipBounds.fTop); REPORTER_ASSERT(reporter, 10 == clipBounds.fBottom); REPORTER_ASSERT(reporter, 10 == clipBounds.fRight); } { SkCanvas* canvas = recorder.beginRecording(10, 10); canvas->clipPath(path, SkRegion::kIntersect_Op); canvas->clipPath(invPath, SkRegion::kIntersect_Op); bool nonEmpty = canvas->getClipDeviceBounds(&clipBounds); REPORTER_ASSERT(reporter, true == nonEmpty); REPORTER_ASSERT(reporter, 7 == clipBounds.fLeft); REPORTER_ASSERT(reporter, 7 == clipBounds.fTop); REPORTER_ASSERT(reporter, 8 == clipBounds.fBottom); REPORTER_ASSERT(reporter, 8 == clipBounds.fRight); } { SkCanvas* canvas = recorder.beginRecording(10, 10); canvas->clipPath(path, SkRegion::kIntersect_Op); canvas->clipPath(invPath, SkRegion::kUnion_Op); bool nonEmpty = canvas->getClipDeviceBounds(&clipBounds); REPORTER_ASSERT(reporter, true == nonEmpty); REPORTER_ASSERT(reporter, 0 == clipBounds.fLeft); REPORTER_ASSERT(reporter, 0 == clipBounds.fTop); REPORTER_ASSERT(reporter, 10 == clipBounds.fBottom); REPORTER_ASSERT(reporter, 10 == clipBounds.fRight); } { SkCanvas* canvas = recorder.beginRecording(10, 10); canvas->clipPath(path, SkRegion::kDifference_Op); bool nonEmpty = canvas->getClipDeviceBounds(&clipBounds); REPORTER_ASSERT(reporter, true == nonEmpty); REPORTER_ASSERT(reporter, 0 == clipBounds.fLeft); REPORTER_ASSERT(reporter, 0 == clipBounds.fTop); REPORTER_ASSERT(reporter, 10 == clipBounds.fBottom); REPORTER_ASSERT(reporter, 10 == clipBounds.fRight); } { SkCanvas* canvas = recorder.beginRecording(10, 10); canvas->clipPath(path, SkRegion::kReverseDifference_Op); bool nonEmpty = canvas->getClipDeviceBounds(&clipBounds); // True clip is actually empty in this case, but the best // determination we can make using only bounds as input is that the // clip is included in the bounds of 'path'. REPORTER_ASSERT(reporter, true == nonEmpty); REPORTER_ASSERT(reporter, 7 == clipBounds.fLeft); REPORTER_ASSERT(reporter, 7 == clipBounds.fTop); REPORTER_ASSERT(reporter, 8 == clipBounds.fBottom); REPORTER_ASSERT(reporter, 8 == clipBounds.fRight); } { SkCanvas* canvas = recorder.beginRecording(10, 10); canvas->clipPath(path, SkRegion::kIntersect_Op); canvas->clipPath(path2, SkRegion::kXOR_Op); bool nonEmpty = canvas->getClipDeviceBounds(&clipBounds); REPORTER_ASSERT(reporter, true == nonEmpty); REPORTER_ASSERT(reporter, 6 == clipBounds.fLeft); REPORTER_ASSERT(reporter, 6 == clipBounds.fTop); REPORTER_ASSERT(reporter, 8 == clipBounds.fBottom); REPORTER_ASSERT(reporter, 8 == clipBounds.fRight); } }
virtual void onDrawContent(SkCanvas* canvas) { SkPath path; path.moveTo(SkIntToScalar(0), SkIntToScalar(50)); path.quadTo(SkIntToScalar(0), SkIntToScalar(0), SkIntToScalar(50), SkIntToScalar(0)); path.lineTo(SkIntToScalar(175), SkIntToScalar(0)); path.quadTo(SkIntToScalar(200), SkIntToScalar(0), SkIntToScalar(200), SkIntToScalar(25)); path.lineTo(SkIntToScalar(200), SkIntToScalar(150)); path.quadTo(SkIntToScalar(200), SkIntToScalar(200), SkIntToScalar(150), SkIntToScalar(200)); path.lineTo(SkIntToScalar(0), SkIntToScalar(200)); path.close(); path.moveTo(SkIntToScalar(50), SkIntToScalar(50)); path.lineTo(SkIntToScalar(150), SkIntToScalar(50)); path.lineTo(SkIntToScalar(150), SkIntToScalar(125)); path.quadTo(SkIntToScalar(150), SkIntToScalar(150), SkIntToScalar(125), SkIntToScalar(150)); path.lineTo(SkIntToScalar(50), SkIntToScalar(150)); path.close(); path.setFillType(SkPath::kEvenOdd_FillType); SkColor pathColor = SK_ColorBLACK; SkPaint pathPaint; pathPaint.setAntiAlias(true); pathPaint.setColor(pathColor); SkPath clipA; clipA.moveTo(SkIntToScalar(10), SkIntToScalar(20)); clipA.lineTo(SkIntToScalar(165), SkIntToScalar(22)); clipA.lineTo(SkIntToScalar(70), SkIntToScalar(105)); clipA.lineTo(SkIntToScalar(165), SkIntToScalar(177)); clipA.lineTo(SkIntToScalar(-5), SkIntToScalar(180)); clipA.close(); SkColor colorA = SK_ColorCYAN; SkPath clipB; clipB.moveTo(SkIntToScalar(40), SkIntToScalar(10)); clipB.lineTo(SkIntToScalar(190), SkIntToScalar(15)); clipB.lineTo(SkIntToScalar(195), SkIntToScalar(190)); clipB.lineTo(SkIntToScalar(40), SkIntToScalar(185)); clipB.lineTo(SkIntToScalar(155), SkIntToScalar(100)); clipB.close(); SkColor colorB = SK_ColorRED; SkPaint paint; paint.setAntiAlias(true); paint.setStyle(SkPaint::kStroke_Style); paint.setStrokeWidth(0); canvas->translate(SkIntToScalar(10),SkIntToScalar(10)); canvas->drawPath(path, pathPaint); paint.setColor(colorA); canvas->drawPath(clipA, paint); paint.setColor(colorB); canvas->drawPath(clipB, paint); static const struct { SkRegion::Op fOp; const char* fName; } gOps[] = { //extra spaces in names for measureText {SkRegion::kIntersect_Op, "Isect "}, {SkRegion::kDifference_Op, "Diff " }, {SkRegion::kUnion_Op, "Union "}, {SkRegion::kXOR_Op, "Xor " }, {SkRegion::kReverseDifference_Op, "RDiff "} }; canvas->translate(0, SkIntToScalar(40)); canvas->scale(3 * SK_Scalar1 / 4, 3 * SK_Scalar1 / 4); canvas->save(); for (int invA = 0; invA < 2; ++invA) { for (size_t op = 0; op < SK_ARRAY_COUNT(gOps); ++op) { int idx = invA * SK_ARRAY_COUNT(gOps) + op; if (!(idx % 3)) { canvas->restore(); canvas->translate(0, SkIntToScalar(250)); canvas->save(); } canvas->save(); // set clip clipA.setFillType(invA ? SkPath::kInverseEvenOdd_FillType : SkPath::kEvenOdd_FillType); canvas->clipPath(clipA); canvas->clipPath(clipB, gOps[op].fOp); // draw path clipped canvas->drawPath(path, pathPaint); canvas->restore(); // draw path in hairline paint.setColor(pathColor); canvas->drawPath(path, paint); // draw clips in hair line paint.setColor(colorA); canvas->drawPath(clipA, paint); paint.setColor(colorB); canvas->drawPath(clipB, paint); paint.setTextSize(SkIntToScalar(20)); SkScalar txtX = SkIntToScalar(55); paint.setColor(colorA); const char* aTxt = invA ? "InverseA " : "A "; canvas->drawText(aTxt, strlen(aTxt), txtX, SkIntToScalar(220), paint); txtX += paint.measureText(aTxt, strlen(aTxt)); paint.setColor(SK_ColorBLACK); canvas->drawText(gOps[op].fName, strlen(gOps[op].fName), txtX, SkIntToScalar(220), paint); txtX += paint.measureText(gOps[op].fName, strlen(gOps[op].fName)); paint.setColor(colorB); canvas->drawText("B", 1, txtX, SkIntToScalar(220), paint); canvas->translate(SkIntToScalar(250),0); } } canvas->restore(); }
bool GrStencilAndCoverPathRenderer::onDrawPath(const DrawPathArgs& args) { GR_AUDIT_TRAIL_AUTO_FRAME(args.fDrawContext->auditTrail(), "GrStencilAndCoverPathRenderer::onDrawPath"); SkASSERT(!args.fPaint->isAntiAlias() || args.fDrawContext->isStencilBufferMultisampled()); SkASSERT(!args.fShape->style().strokeRec().isHairlineStyle()); const SkMatrix& viewMatrix = *args.fViewMatrix; SkPath path; args.fShape->asPath(&path); SkAutoTUnref<GrPath> p(get_gr_path(fResourceProvider, path, args.fShape->style())); if (path.isInverseFillType()) { SkMatrix invert = SkMatrix::I(); SkRect bounds = SkRect::MakeLTRB(0, 0, SkIntToScalar(args.fDrawContext->width()), SkIntToScalar(args.fDrawContext->height())); SkMatrix vmi; // mapRect through persp matrix may not be correct if (!viewMatrix.hasPerspective() && viewMatrix.invert(&vmi)) { vmi.mapRect(&bounds); // theoretically could set bloat = 0, instead leave it because of matrix inversion // precision. SkScalar bloat = viewMatrix.getMaxScale() * SK_ScalarHalf; bounds.outset(bloat, bloat); } else { if (!viewMatrix.invert(&invert)) { return false; } } const SkMatrix& viewM = viewMatrix.hasPerspective() ? SkMatrix::I() : viewMatrix; SkAutoTUnref<GrDrawBatch> coverBatch( GrRectBatchFactory::CreateNonAAFill(args.fPaint->getColor(), viewM, bounds, nullptr, &invert)); // fake inverse with a stencil and cover args.fDrawContext->drawContextPriv().stencilPath(*args.fClip, args.fPaint->isAntiAlias(), viewMatrix, p); { static constexpr GrUserStencilSettings kInvertedCoverPass( GrUserStencilSettings::StaticInit< 0x0000, // We know our rect will hit pixels outside the clip and the user bits will // be 0 outside the clip. So we can't just fill where the user bits are 0. We // also need to check that the clip bit is set. GrUserStencilTest::kEqualIfInClip, 0xffff, GrUserStencilOp::kKeep, GrUserStencilOp::kZero, 0xffff>() ); GrPipelineBuilder pipelineBuilder(*args.fPaint, args.fPaint->isAntiAlias() && !args.fDrawContext->hasMixedSamples()); pipelineBuilder.setUserStencil(&kInvertedCoverPass); args.fDrawContext->drawBatch(pipelineBuilder, *args.fClip, coverBatch); } } else { static constexpr GrUserStencilSettings kCoverPass( GrUserStencilSettings::StaticInit< 0x0000, GrUserStencilTest::kNotEqual, 0xffff, GrUserStencilOp::kZero, GrUserStencilOp::kKeep, 0xffff>() ); SkAutoTUnref<GrDrawBatch> batch( GrDrawPathBatch::Create(viewMatrix, args.fPaint->getColor(), p->getFillType(), p)); GrPipelineBuilder pipelineBuilder(*args.fPaint, args.fPaint->isAntiAlias()); pipelineBuilder.setUserStencil(&kCoverPass); if (args.fAntiAlias) { SkASSERT(args.fDrawContext->isStencilBufferMultisampled()); pipelineBuilder.enableState(GrPipelineBuilder::kHWAntialias_Flag); } args.fDrawContext->drawBatch(pipelineBuilder, *args.fClip, batch); } return true; }
/** * Generates the lines and quads to be rendered. Lines are always recorded in * device space. We will do a device space bloat to account for the 1pixel * thickness. * Quads are recorded in device space unless m contains * perspective, then in they are in src space. We do this because we will * subdivide large quads to reduce over-fill. This subdivision has to be * performed before applying the perspective matrix. */ static int gather_lines_and_quads(const SkPath& path, const SkMatrix& m, const SkIRect& devClipBounds, GrAAHairLinePathRenderer::PtArray* lines, GrAAHairLinePathRenderer::PtArray* quads, GrAAHairLinePathRenderer::PtArray* conics, GrAAHairLinePathRenderer::IntArray* quadSubdivCnts, GrAAHairLinePathRenderer::FloatArray* conicWeights) { SkPath::Iter iter(path, false); int totalQuadCount = 0; SkRect bounds; SkIRect ibounds; bool persp = m.hasPerspective(); for (;;) { SkPoint pathPts[4]; SkPoint devPts[4]; SkPath::Verb verb = iter.next(pathPts); switch (verb) { case SkPath::kConic_Verb: { SkConic dst[4]; // We chop the conics to create tighter clipping to hide error // that appears near max curvature of very thin conics. Thin // hyperbolas with high weight still show error. int conicCnt = chop_conic(pathPts, dst, iter.conicWeight()); for (int i = 0; i < conicCnt; ++i) { SkPoint* chopPnts = dst[i].fPts; m.mapPoints(devPts, chopPnts, 3); bounds.setBounds(devPts, 3); bounds.outset(SK_Scalar1, SK_Scalar1); bounds.roundOut(&ibounds); if (SkIRect::Intersects(devClipBounds, ibounds)) { if (is_degen_quad_or_conic(devPts)) { SkPoint* pts = lines->push_back_n(4); pts[0] = devPts[0]; pts[1] = devPts[1]; pts[2] = devPts[1]; pts[3] = devPts[2]; } else { // when in perspective keep conics in src space SkPoint* cPts = persp ? chopPnts : devPts; SkPoint* pts = conics->push_back_n(3); pts[0] = cPts[0]; pts[1] = cPts[1]; pts[2] = cPts[2]; conicWeights->push_back() = dst[i].fW; } } } break; } case SkPath::kMove_Verb: break; case SkPath::kLine_Verb: m.mapPoints(devPts, pathPts, 2); bounds.setBounds(devPts, 2); bounds.outset(SK_Scalar1, SK_Scalar1); bounds.roundOut(&ibounds); if (SkIRect::Intersects(devClipBounds, ibounds)) { SkPoint* pts = lines->push_back_n(2); pts[0] = devPts[0]; pts[1] = devPts[1]; } break; case SkPath::kQuad_Verb: { SkPoint choppedPts[5]; // Chopping the quad helps when the quad is either degenerate or nearly degenerate. // When it is degenerate it allows the approximation with lines to work since the // chop point (if there is one) will be at the parabola's vertex. In the nearly // degenerate the QuadUVMatrix computed for the points is almost singular which // can cause rendering artifacts. int n = SkChopQuadAtMaxCurvature(pathPts, choppedPts); for (int i = 0; i < n; ++i) { SkPoint* quadPts = choppedPts + i * 2; m.mapPoints(devPts, quadPts, 3); bounds.setBounds(devPts, 3); bounds.outset(SK_Scalar1, SK_Scalar1); bounds.roundOut(&ibounds); if (SkIRect::Intersects(devClipBounds, ibounds)) { int subdiv = num_quad_subdivs(devPts); SkASSERT(subdiv >= -1); if (-1 == subdiv) { SkPoint* pts = lines->push_back_n(4); pts[0] = devPts[0]; pts[1] = devPts[1]; pts[2] = devPts[1]; pts[3] = devPts[2]; } else { // when in perspective keep quads in src space SkPoint* qPts = persp ? quadPts : devPts; SkPoint* pts = quads->push_back_n(3); pts[0] = qPts[0]; pts[1] = qPts[1]; pts[2] = qPts[2]; quadSubdivCnts->push_back() = subdiv; totalQuadCount += 1 << subdiv; } } } break; } case SkPath::kCubic_Verb: m.mapPoints(devPts, pathPts, 4); bounds.setBounds(devPts, 4); bounds.outset(SK_Scalar1, SK_Scalar1); bounds.roundOut(&ibounds); if (SkIRect::Intersects(devClipBounds, ibounds)) { PREALLOC_PTARRAY(32) q; // We convert cubics to quadratics (for now). // In perspective have to do conversion in src space. if (persp) { SkScalar tolScale = GrPathUtils::scaleToleranceToSrc(SK_Scalar1, m, path.getBounds()); GrPathUtils::convertCubicToQuads(pathPts, tolScale, &q); } else { GrPathUtils::convertCubicToQuads(devPts, SK_Scalar1, &q); } for (int i = 0; i < q.count(); i += 3) { SkPoint* qInDevSpace; // bounds has to be calculated in device space, but q is // in src space when there is perspective. if (persp) { m.mapPoints(devPts, &q[i], 3); bounds.setBounds(devPts, 3); qInDevSpace = devPts; } else { bounds.setBounds(&q[i], 3); qInDevSpace = &q[i]; } bounds.outset(SK_Scalar1, SK_Scalar1); bounds.roundOut(&ibounds); if (SkIRect::Intersects(devClipBounds, ibounds)) { int subdiv = num_quad_subdivs(qInDevSpace); SkASSERT(subdiv >= -1); if (-1 == subdiv) { SkPoint* pts = lines->push_back_n(4); // lines should always be in device coords pts[0] = qInDevSpace[0]; pts[1] = qInDevSpace[1]; pts[2] = qInDevSpace[1]; pts[3] = qInDevSpace[2]; } else { SkPoint* pts = quads->push_back_n(3); // q is already in src space when there is no // perspective and dev coords otherwise. pts[0] = q[0 + i]; pts[1] = q[1 + i]; pts[2] = q[2 + i]; quadSubdivCnts->push_back() = subdiv; totalQuadCount += 1 << subdiv; } } } } break; case SkPath::kClose_Verb: break; case SkPath::kDone_Verb: return totalQuadCount; } } }
bool GrDefaultPathRenderer::internalDrawPath(GrDrawTarget* target, GrPipelineBuilder* pipelineBuilder, GrColor color, const SkMatrix& viewMatrix, const SkPath& path, const GrStrokeInfo& origStroke, bool stencilOnly) { SkTCopyOnFirstWrite<GrStrokeInfo> stroke(origStroke); SkScalar hairlineCoverage; uint8_t newCoverage = 0xff; if (IsStrokeHairlineOrEquivalent(*stroke, viewMatrix, &hairlineCoverage)) { newCoverage = SkScalarRoundToInt(hairlineCoverage * 0xff); if (!stroke->isHairlineStyle()) { stroke.writable()->setHairlineStyle(); } } const bool isHairline = stroke->isHairlineStyle(); // Save the current xp on the draw state so we can reset it if needed SkAutoTUnref<const GrXPFactory> backupXPFactory(SkRef(pipelineBuilder->getXPFactory())); // face culling doesn't make sense here SkASSERT(GrPipelineBuilder::kBoth_DrawFace == pipelineBuilder->getDrawFace()); int passCount = 0; const GrStencilSettings* passes[3]; GrPipelineBuilder::DrawFace drawFace[3]; bool reverse = false; bool lastPassIsBounds; if (isHairline) { passCount = 1; if (stencilOnly) { passes[0] = &gDirectToStencil; } else { passes[0] = NULL; } lastPassIsBounds = false; drawFace[0] = GrPipelineBuilder::kBoth_DrawFace; } else { if (single_pass_path(path, *stroke)) { passCount = 1; if (stencilOnly) { passes[0] = &gDirectToStencil; } else { passes[0] = NULL; } drawFace[0] = GrPipelineBuilder::kBoth_DrawFace; lastPassIsBounds = false; } else { switch (path.getFillType()) { case SkPath::kInverseEvenOdd_FillType: reverse = true; // fallthrough case SkPath::kEvenOdd_FillType: passes[0] = &gEOStencilPass; if (stencilOnly) { passCount = 1; lastPassIsBounds = false; } else { passCount = 2; lastPassIsBounds = true; if (reverse) { passes[1] = &gInvEOColorPass; } else { passes[1] = &gEOColorPass; } } drawFace[0] = drawFace[1] = GrPipelineBuilder::kBoth_DrawFace; break; case SkPath::kInverseWinding_FillType: reverse = true; // fallthrough case SkPath::kWinding_FillType: if (fSeparateStencil) { if (fStencilWrapOps) { passes[0] = &gWindStencilSeparateWithWrap; } else { passes[0] = &gWindStencilSeparateNoWrap; } passCount = 2; drawFace[0] = GrPipelineBuilder::kBoth_DrawFace; } else { if (fStencilWrapOps) { passes[0] = &gWindSingleStencilWithWrapInc; passes[1] = &gWindSingleStencilWithWrapDec; } else { passes[0] = &gWindSingleStencilNoWrapInc; passes[1] = &gWindSingleStencilNoWrapDec; } // which is cw and which is ccw is arbitrary. drawFace[0] = GrPipelineBuilder::kCW_DrawFace; drawFace[1] = GrPipelineBuilder::kCCW_DrawFace; passCount = 3; } if (stencilOnly) { lastPassIsBounds = false; --passCount; } else { lastPassIsBounds = true; drawFace[passCount-1] = GrPipelineBuilder::kBoth_DrawFace; if (reverse) { passes[passCount-1] = &gInvWindColorPass; } else { passes[passCount-1] = &gWindColorPass; } } break; default: SkDEBUGFAIL("Unknown path fFill!"); return false; } } } SkScalar tol = GrPathUtils::kDefaultTolerance; SkScalar srcSpaceTol = GrPathUtils::scaleToleranceToSrc(tol, viewMatrix, path.getBounds()); SkRect devBounds; GetPathDevBounds(path, pipelineBuilder->getRenderTarget(), viewMatrix, &devBounds); for (int p = 0; p < passCount; ++p) { pipelineBuilder->setDrawFace(drawFace[p]); if (passes[p]) { *pipelineBuilder->stencil() = *passes[p]; } if (lastPassIsBounds && (p == passCount-1)) { // Reset the XP Factory on pipelineBuilder pipelineBuilder->setXPFactory(backupXPFactory); SkRect bounds; SkMatrix localMatrix = SkMatrix::I(); if (reverse) { SkASSERT(pipelineBuilder->getRenderTarget()); // draw over the dev bounds (which will be the whole dst surface for inv fill). bounds = devBounds; SkMatrix vmi; // mapRect through persp matrix may not be correct if (!viewMatrix.hasPerspective() && viewMatrix.invert(&vmi)) { vmi.mapRect(&bounds); } else { if (!viewMatrix.invert(&localMatrix)) { return false; } } } else { bounds = path.getBounds(); } const SkMatrix& viewM = (reverse && viewMatrix.hasPerspective()) ? SkMatrix::I() : viewMatrix; target->drawBWRect(*pipelineBuilder, color, viewM, bounds, NULL, &localMatrix); } else { if (passCount > 1) { pipelineBuilder->setDisableColorXPFactory(); } DefaultPathBatch::Geometry geometry; geometry.fColor = color; geometry.fPath = path; geometry.fTolerance = srcSpaceTol; SkAutoTUnref<GrBatch> batch(DefaultPathBatch::Create(geometry, newCoverage, viewMatrix, isHairline, devBounds)); target->drawBatch(*pipelineBuilder, batch); } } return true; }
void onOnceBeforeDraw() override { const SkRect fieldBounds = kBounds.makeOutset(kBallSize / 2, kBallSize / 2); const SkRRect ball = SkRRect::MakeOval(SkRect::MakeWH(kBallSize, kBallSize)); const SkRRect paddle = SkRRect::MakeRectXY(SkRect::MakeWH(kPaddleSize.width(), kPaddleSize.height()), kPaddleSize.width() / 2, kPaddleSize.width() / 2); fBall.initialize(ball, SK_ColorGREEN, SkPoint::Make(kBounds.centerX(), kBounds.centerY()), SkVector::Make(fRand.nextRangeScalar(kBallSpeedMin, kBallSpeedMax), fRand.nextRangeScalar(kBallSpeedMin, kBallSpeedMax))); fPaddle0.initialize(paddle, SK_ColorBLUE, SkPoint::Make(fieldBounds.left() - kPaddleSize.width() / 2, fieldBounds.centerY()), SkVector::Make(0, 0)); fPaddle1.initialize(paddle, SK_ColorRED, SkPoint::Make(fieldBounds.right() + kPaddleSize.width() / 2, fieldBounds.centerY()), SkVector::Make(0, 0)); // Background decoration. SkPath bgPath; bgPath.moveTo(kBounds.left() , fieldBounds.top()); bgPath.lineTo(kBounds.right(), fieldBounds.top()); bgPath.moveTo(kBounds.left() , fieldBounds.bottom()); bgPath.lineTo(kBounds.right(), fieldBounds.bottom()); // TODO: stroke-dash support would come in handy right about now. for (uint32_t i = 0; i < kBackgroundDashCount; ++i) { bgPath.moveTo(kBounds.centerX(), kBounds.top() + (i + 0.25f) * kBounds.height() / kBackgroundDashCount); bgPath.lineTo(kBounds.centerX(), kBounds.top() + (i + 0.75f) * kBounds.height() / kBackgroundDashCount); } sk_sp<SkSVGPath> bg = SkSVGPath::Make(); bg->setPath(bgPath); bg->setFill(SkSVGPaint(SkSVGPaint::Type::kNone)); bg->setStroke(SkSVGPaint(SkSVGColorType(SK_ColorBLACK))); bg->setStrokeWidth(SkSVGLength(kBackgroundStroke)); // Build the SVG DOM tree. sk_sp<SkSVGSVG> root = SkSVGSVG::Make(); root->appendChild(std::move(bg)); root->appendChild(fPaddle0.shadowNode); root->appendChild(fPaddle1.shadowNode); root->appendChild(fBall.shadowNode); root->appendChild(fPaddle0.objectNode); root->appendChild(fPaddle1.objectNode); root->appendChild(fBall.objectNode); // Handle everything in a normalized 1x1 space. root->setViewBox(SkSVGViewBoxType(SkRect::MakeWH(1, 1))); fDom = sk_sp<SkSVGDOM>(new SkSVGDOM()); fDom->setContainerSize(SkSize::Make(this->width(), this->height())); fDom->setRoot(std::move(root)); // Off we go. this->updatePaddleStrategy(); }
void onPrepareDraws(Target* target) const override { int instanceCount = fGeoData.count(); SkMatrix invert; if (this->usesLocalCoords() && !this->viewMatrix().invert(&invert)) { SkDebugf("Could not invert viewmatrix\n"); return; } // Setup GrGeometryProcessors SkAutoTUnref<GrPLSGeometryProcessor> triangleProcessor( PLSAATriangleEffect::Create(invert, this->usesLocalCoords())); SkAutoTUnref<GrPLSGeometryProcessor> quadProcessor( PLSQuadEdgeEffect::Create(invert, this->usesLocalCoords())); GrResourceProvider* rp = target->resourceProvider(); for (int i = 0; i < instanceCount; ++i) { const Geometry& args = fGeoData[i]; SkRect bounds = args.fPath.getBounds(); args.fViewMatrix.mapRect(&bounds); bounds.fLeft = SkScalarFloorToScalar(bounds.fLeft); bounds.fTop = SkScalarFloorToScalar(bounds.fTop); bounds.fRight = SkScalarCeilToScalar(bounds.fRight); bounds.fBottom = SkScalarCeilToScalar(bounds.fBottom); triangleProcessor->setBounds(bounds); quadProcessor->setBounds(bounds); // We use the fact that SkPath::transform path does subdivision based on // perspective. Otherwise, we apply the view matrix when copying to the // segment representation. const SkMatrix* viewMatrix = &args.fViewMatrix; // We avoid initializing the path unless we have to const SkPath* pathPtr = &args.fPath; SkTLazy<SkPath> tmpPath; if (viewMatrix->hasPerspective()) { SkPath* tmpPathPtr = tmpPath.init(*pathPtr); tmpPathPtr->setIsVolatile(true); tmpPathPtr->transform(*viewMatrix); viewMatrix = &SkMatrix::I(); pathPtr = tmpPathPtr; } GrMesh mesh; PLSVertices triVertices; PLSVertices quadVertices; if (!get_geometry(*pathPtr, *viewMatrix, triVertices, quadVertices, rp, bounds)) { continue; } if (triVertices.count()) { const GrBuffer* triVertexBuffer; int firstTriVertex; size_t triStride = triangleProcessor->getVertexStride(); PLSVertex* triVerts = reinterpret_cast<PLSVertex*>(target->makeVertexSpace( triStride, triVertices.count(), &triVertexBuffer, &firstTriVertex)); if (!triVerts) { SkDebugf("Could not allocate vertices\n"); return; } for (int i = 0; i < triVertices.count(); ++i) { triVerts[i] = triVertices[i]; } mesh.init(kTriangles_GrPrimitiveType, triVertexBuffer, firstTriVertex, triVertices.count()); target->draw(triangleProcessor, mesh); } if (quadVertices.count()) { const GrBuffer* quadVertexBuffer; int firstQuadVertex; size_t quadStride = quadProcessor->getVertexStride(); PLSVertex* quadVerts = reinterpret_cast<PLSVertex*>(target->makeVertexSpace( quadStride, quadVertices.count(), &quadVertexBuffer, &firstQuadVertex)); if (!quadVerts) { SkDebugf("Could not allocate vertices\n"); return; } for (int i = 0; i < quadVertices.count(); ++i) { quadVerts[i] = quadVertices[i]; } mesh.init(kTriangles_GrPrimitiveType, quadVertexBuffer, firstQuadVertex, quadVertices.count()); target->draw(quadProcessor, mesh); } SkAutoTUnref<GrGeometryProcessor> finishProcessor( PLSFinishEffect::Create(this->color(), pathPtr->getFillType() == SkPath::FillType::kEvenOdd_FillType, invert, this->usesLocalCoords())); const GrBuffer* rectVertexBuffer; size_t finishStride = finishProcessor->getVertexStride(); int firstRectVertex; static const int kRectVertexCount = 6; SkPoint* rectVerts = reinterpret_cast<SkPoint*>(target->makeVertexSpace( finishStride, kRectVertexCount, &rectVertexBuffer, &firstRectVertex)); if (!rectVerts) { SkDebugf("Could not allocate vertices\n"); return; } rectVerts[0] = { bounds.fLeft, bounds.fTop }; rectVerts[1] = { bounds.fLeft, bounds.fBottom }; rectVerts[2] = { bounds.fRight, bounds.fBottom }; rectVerts[3] = { bounds.fLeft, bounds.fTop }; rectVerts[4] = { bounds.fRight, bounds.fTop }; rectVerts[5] = { bounds.fRight, bounds.fBottom }; mesh.init(kTriangles_GrPrimitiveType, rectVertexBuffer, firstRectVertex, kRectVertexCount); target->draw(finishProcessor, mesh); } }
// A degenerate segments case which exercises inactive edges being // made active by splitting. static SkPath create_path_13() { SkPath path; path.moveTo(690.62127685546875f, 509.25555419921875f); path.lineTo(99.336181640625f, 511.71405029296875f); path.lineTo(708.362548828125f, 512.4349365234375f); path.lineTo(729.9940185546875f, 516.3114013671875f); path.lineTo(738.708984375f, 518.76995849609375f); path.lineTo(678.3463134765625f, 510.0819091796875f); path.lineTo(681.21795654296875f, 504.81378173828125f); path.moveTo(758.52764892578125f, 521.55963134765625f); path.lineTo(719.1549072265625f, 514.50372314453125f); path.lineTo(689.59063720703125f, 512.0628662109375f); path.lineTo(679.78216552734375f, 507.447845458984375f); return path; }
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; }
static sk_sp<SkPathEffect> MakeDotEffect(SkScalar radius, const SkMatrix& matrix) { SkPath path; path.addCircle(0, 0, radius); return SkPath2DPathEffect::Make(matrix, path); }
void WRasterImage::Impl::drawPlainPath(SkPath &p, const WPainterPath& path) { const std::vector<WPainterPath::Segment>& segments = path.segments(); if (segments.size() > 0 && segments[0].type() != WPainterPath::Segment::MoveTo) p.moveTo(SkDoubleToScalar(0), SkDoubleToScalar(0)); for (unsigned i = 0; i < segments.size(); ++i) { const WPainterPath::Segment s = segments[i]; switch (s.type()) { case WPainterPath::Segment::MoveTo: p.moveTo(SkDoubleToScalar(s.x()), SkDoubleToScalar(s.y())); break; case WPainterPath::Segment::LineTo: p.lineTo(SkDoubleToScalar(s.x()), SkDoubleToScalar(s.y())); break; case WPainterPath::Segment::CubicC1: { const double x1 = s.x(); const double y1 = s.y(); const double x2 = segments[i+1].x(); const double y2 = segments[i+1].y(); const double x3 = segments[i+2].x(); const double y3 = segments[i+2].y(); p.cubicTo(SkDoubleToScalar(x1), SkDoubleToScalar(y1), SkDoubleToScalar(x2), SkDoubleToScalar(y2), SkDoubleToScalar(x3), SkDoubleToScalar(y3)); i += 2; break; } case WPainterPath::Segment::CubicC2: case WPainterPath::Segment::CubicEnd: assert(false); case WPainterPath::Segment::ArcC: { const double x = s.x(); const double y = s.y(); const double width = segments[i+1].x(); const double height = segments[i+1].y(); const double startAngle = segments[i+2].x(); const double sweepAngle = segments[i+2].y(); SkRect rect = SkRect::MakeXYWH(SkDoubleToScalar(x - width), SkDoubleToScalar(y - height), SkDoubleToScalar(width * 2.0), SkDoubleToScalar(height * 2.0)); if (sweepAngle != 360) p.arcTo(rect, SkDoubleToScalar(-startAngle), SkDoubleToScalar(-sweepAngle), false); else p.addOval(rect, SkPath::kCCW_Direction); i += 2; break; } case WPainterPath::Segment::ArcR: case WPainterPath::Segment::ArcAngleSweep: assert(false); case WPainterPath::Segment::QuadC: { const double x1 = s.x(); const double y1 = s.y(); const double x2 = segments[i+1].x(); const double y2 = segments[i+1].y(); p.quadTo(SkDoubleToScalar(x1), SkDoubleToScalar(y1), SkDoubleToScalar(x2), SkDoubleToScalar(y2)); i += 1; break; } case WPainterPath::Segment::QuadEnd: assert(false); } } }
bool GrStencilAndCoverPathRenderer::onDrawPath(const SkPath& path, const SkStroke& stroke, GrDrawTarget* target, bool antiAlias) { GrAssert(!antiAlias); GrAssert(0 != stroke.getWidthIfStroked()); GrDrawState* drawState = target->drawState(); GrAssert(drawState->getStencil().isDisabled()); SkAutoTUnref<GrPath> p(fGpu->createPath(path)); SkPath::FillType nonInvertedFill = SkPath::NonInverseFill(path.getFillType()); target->stencilPath(p, stroke, nonInvertedFill); // TODO: Use built in cover operation rather than a rect draw. This will require making our // fragment shaders be able to eat varyings generated by a matrix. // fill the path, zero out the stencil GrRect bounds = p->getBounds(); SkScalar bloat = drawState->getViewMatrix().getMaxStretch() * SK_ScalarHalf; GrDrawState::AutoDeviceCoordDraw adcd; if (nonInvertedFill == path.getFillType()) { GR_STATIC_CONST_SAME_STENCIL(kStencilPass, kZero_StencilOp, kZero_StencilOp, kNotEqual_StencilFunc, 0xffff, 0x0000, 0xffff); *drawState->stencil() = kStencilPass; } else { GR_STATIC_CONST_SAME_STENCIL(kInvertedStencilPass, kZero_StencilOp, kZero_StencilOp, // We know our rect will hit pixels outside the clip and the user bits will be 0 // outside the clip. So we can't just fill where the user bits are 0. We also need to // check that the clip bit is set. kEqualIfInClip_StencilFunc, 0xffff, 0x0000, 0xffff); SkMatrix vmi; bounds.setLTRB(0, 0, SkIntToScalar(drawState->getRenderTarget()->width()), SkIntToScalar(drawState->getRenderTarget()->height())); // mapRect through persp matrix may not be correct if (!drawState->getViewMatrix().hasPerspective() && drawState->getViewInverse(&vmi)) { vmi.mapRect(&bounds); // theoretically could set bloat = 0, instead leave it because of matrix inversion // precision. } else { adcd.set(drawState); bloat = 0; } *drawState->stencil() = kInvertedStencilPass; } bounds.outset(bloat, bloat); target->drawSimpleRect(bounds, NULL); target->drawState()->stencil()->setDisabled(); return true; }
void SkPathStroker::cubic_to(const SkPoint pts[4], const SkVector& normalAB, const SkVector& unitNormalAB, SkVector* normalCD, SkVector* unitNormalCD, int subDivide) { SkVector ab = pts[1] - pts[0]; SkVector cd = pts[3] - pts[2]; SkVector normalBC, unitNormalBC; bool degenerateAB = degenerate_vector(ab); bool degenerateCD = degenerate_vector(cd); if (degenerateAB && degenerateCD) { DRAW_LINE: this->line_to(pts[3], normalAB); *normalCD = normalAB; *unitNormalCD = unitNormalAB; return; } if (degenerateAB) { ab = pts[2] - pts[0]; degenerateAB = degenerate_vector(ab); } if (degenerateCD) { cd = pts[3] - pts[1]; degenerateCD = degenerate_vector(cd); } if (degenerateAB || degenerateCD) { goto DRAW_LINE; } SkAssertResult(set_normal_unitnormal(cd, fRadius, normalCD, unitNormalCD)); bool degenerateBC = !set_normal_unitnormal(pts[1], pts[2], fRadius, &normalBC, &unitNormalBC); if (degenerateBC || normals_too_curvy(unitNormalAB, unitNormalBC) || normals_too_curvy(unitNormalBC, *unitNormalCD)) { // subdivide if we can if (--subDivide < 0) { goto DRAW_LINE; } SkPoint tmp[7]; SkVector norm, unit, dummy, unitDummy; SkChopCubicAtHalf(pts, tmp); this->cubic_to(&tmp[0], normalAB, unitNormalAB, &norm, &unit, subDivide); // we use dummys since we already have a valid (and more accurate) // normals for CD this->cubic_to(&tmp[3], norm, unit, &dummy, &unitDummy, subDivide); } else { SkVector normalB, normalC; // need normals to inset/outset the off-curve pts B and C if (0) { // this is normal to the line between our adjacent pts normalB = pts[2] - pts[0]; normalB.rotateCCW(); SkAssertResult(normalB.setLength(fRadius)); normalC = pts[3] - pts[1]; normalC.rotateCCW(); SkAssertResult(normalC.setLength(fRadius)); } else { // miter-join SkVector unitBC = pts[2] - pts[1]; unitBC.normalize(); unitBC.rotateCCW(); normalB = unitNormalAB + unitBC; normalC = *unitNormalCD + unitBC; SkScalar dot = SkPoint::DotProduct(unitNormalAB, unitBC); SkAssertResult(normalB.setLength(SkScalarDiv(fRadius, SkScalarSqrt((SK_Scalar1 + dot)/2)))); dot = SkPoint::DotProduct(*unitNormalCD, unitBC); SkAssertResult(normalC.setLength(SkScalarDiv(fRadius, SkScalarSqrt((SK_Scalar1 + dot)/2)))); } fOuter.cubicTo( pts[1].fX + normalB.fX, pts[1].fY + normalB.fY, pts[2].fX + normalC.fX, pts[2].fY + normalC.fY, pts[3].fX + normalCD->fX, pts[3].fY + normalCD->fY); fInner.cubicTo( pts[1].fX - normalB.fX, pts[1].fY - normalB.fY, pts[2].fX - normalC.fX, pts[2].fY - normalC.fY, pts[3].fX - normalCD->fX, pts[3].fY - normalCD->fY); } }
static void test_gpu_veto(skiatest::Reporter* reporter) { SkPictureRecorder recorder; SkCanvas* canvas = recorder.beginRecording(100, 100); { SkPath path; path.moveTo(0, 0); path.lineTo(50, 50); SkScalar intervals[] = { 1.0f, 1.0f }; sk_sp<SkPathEffect> dash(SkDashPathEffect::Make(intervals, 2, 0)); SkPaint paint; paint.setStyle(SkPaint::kStroke_Style); paint.setPathEffect(dash); for (int i = 0; i < 50; ++i) { canvas->drawPath(path, paint); } } sk_sp<SkPicture> picture(recorder.finishRecordingAsPicture()); // path effects currently render an SkPicture undesireable for GPU rendering const char *reason = nullptr; REPORTER_ASSERT(reporter, !SkPictureGpuAnalyzer(picture).suitableForGpuRasterization(&reason)); REPORTER_ASSERT(reporter, reason); canvas = recorder.beginRecording(100, 100); { SkPath path; path.moveTo(0, 0); path.lineTo(0, 50); path.lineTo(25, 25); path.lineTo(50, 50); path.lineTo(50, 0); path.close(); REPORTER_ASSERT(reporter, !path.isConvex()); SkPaint paint; paint.setAntiAlias(true); for (int i = 0; i < 50; ++i) { canvas->drawPath(path, paint); } } picture = recorder.finishRecordingAsPicture(); // A lot of small AA concave paths should be fine for GPU rendering REPORTER_ASSERT(reporter, SkPictureGpuAnalyzer(picture).suitableForGpuRasterization()); canvas = recorder.beginRecording(100, 100); { SkPath path; path.moveTo(0, 0); path.lineTo(0, 100); path.lineTo(50, 50); path.lineTo(100, 100); path.lineTo(100, 0); path.close(); REPORTER_ASSERT(reporter, !path.isConvex()); SkPaint paint; paint.setAntiAlias(true); for (int i = 0; i < 50; ++i) { canvas->drawPath(path, paint); } } picture = recorder.finishRecordingAsPicture(); // A lot of large AA concave paths currently render an SkPicture undesireable for GPU rendering REPORTER_ASSERT(reporter, !SkPictureGpuAnalyzer(picture).suitableForGpuRasterization()); canvas = recorder.beginRecording(100, 100); { SkPath path; path.moveTo(0, 0); path.lineTo(0, 50); path.lineTo(25, 25); path.lineTo(50, 50); path.lineTo(50, 0); path.close(); REPORTER_ASSERT(reporter, !path.isConvex()); SkPaint paint; paint.setAntiAlias(true); paint.setStyle(SkPaint::kStroke_Style); paint.setStrokeWidth(0); for (int i = 0; i < 50; ++i) { canvas->drawPath(path, paint); } } picture = recorder.finishRecordingAsPicture(); // hairline stroked AA concave paths are fine for GPU rendering REPORTER_ASSERT(reporter, SkPictureGpuAnalyzer(picture).suitableForGpuRasterization()); canvas = recorder.beginRecording(100, 100); { SkPaint paint; SkScalar intervals [] = { 10, 20 }; paint.setPathEffect(SkDashPathEffect::Make(intervals, 2, 25)); SkPoint points [2] = { { 0, 0 }, { 100, 0 } }; for (int i = 0; i < 50; ++i) { canvas->drawPoints(SkCanvas::kLines_PointMode, 2, points, paint); } } picture = recorder.finishRecordingAsPicture(); // fast-path dashed effects are fine for GPU rendering ... REPORTER_ASSERT(reporter, SkPictureGpuAnalyzer(picture).suitableForGpuRasterization()); canvas = recorder.beginRecording(100, 100); { SkPaint paint; SkScalar intervals [] = { 10, 20 }; paint.setPathEffect(SkDashPathEffect::Make(intervals, 2, 25)); for (int i = 0; i < 50; ++i) { canvas->drawRect(SkRect::MakeWH(10, 10), paint); } } picture = recorder.finishRecordingAsPicture(); // ... but only when applied to drawPoint() calls REPORTER_ASSERT(reporter, !SkPictureGpuAnalyzer(picture).suitableForGpuRasterization()); canvas = recorder.beginRecording(100, 100); { const SkPath convexClip = make_convex_path(); const SkPath concaveClip = make_concave_path(); for (int i = 0; i < 50; ++i) { canvas->clipPath(convexClip); canvas->clipPath(concaveClip); canvas->clipPath(convexClip, SkRegion::kIntersect_Op, true); canvas->drawRect(SkRect::MakeWH(100, 100), SkPaint()); } } picture = recorder.finishRecordingAsPicture(); // Convex clips and non-AA concave clips are fine on the GPU. REPORTER_ASSERT(reporter, SkPictureGpuAnalyzer(picture).suitableForGpuRasterization()); canvas = recorder.beginRecording(100, 100); { const SkPath concaveClip = make_concave_path(); for (int i = 0; i < 50; ++i) { canvas->clipPath(concaveClip, SkRegion::kIntersect_Op, true); canvas->drawRect(SkRect::MakeWH(100, 100), SkPaint()); } } picture = recorder.finishRecordingAsPicture(); // ... but AA concave clips are not. REPORTER_ASSERT(reporter, !SkPictureGpuAnalyzer(picture).suitableForGpuRasterization()); // Nest the previous picture inside a new one. canvas = recorder.beginRecording(100, 100); { canvas->drawPicture(picture); } picture = recorder.finishRecordingAsPicture(); REPORTER_ASSERT(reporter, !SkPictureGpuAnalyzer(picture).suitableForGpuRasterization()); }
void SkStroke::strokePath(const SkPath& src, SkPath* dst) const { SkASSERT(&src != NULL && dst != NULL); SkScalar radius = SkScalarHalf(fWidth); dst->reset(); if (radius <= 0) { return; } #ifdef SK_SCALAR_IS_FIXED void (*proc)(SkPoint pts[], int count) = identity_proc; if (needs_to_shrink(src)) { proc = shift_down_2_proc; radius >>= 2; if (radius == 0) { return; } } #endif SkPathStroker stroker(radius, fMiterLimit, this->getCap(), this->getJoin()); SkPath::Iter iter(src, false); SkPoint pts[4]; SkPath::Verb verb, lastSegment = SkPath::kMove_Verb; while ((verb = iter.next(pts)) != SkPath::kDone_Verb) { switch (verb) { case SkPath::kMove_Verb: APPLY_PROC(proc, &pts[0], 1); stroker.moveTo(pts[0]); break; case SkPath::kLine_Verb: APPLY_PROC(proc, &pts[1], 1); stroker.lineTo(pts[1]); lastSegment = verb; break; case SkPath::kQuad_Verb: APPLY_PROC(proc, &pts[1], 2); stroker.quadTo(pts[1], pts[2]); lastSegment = verb; break; case SkPath::kCubic_Verb: APPLY_PROC(proc, &pts[1], 3); stroker.cubicTo(pts[1], pts[2], pts[3]); lastSegment = verb; break; case SkPath::kClose_Verb: stroker.close(lastSegment == SkPath::kLine_Verb); break; default: break; } } stroker.done(dst, lastSegment == SkPath::kLine_Verb); #ifdef SK_SCALAR_IS_FIXED // undo our previous down_shift if (shift_down_2_proc == proc) { // need a real shift methid on path. antialias paths could use this too SkMatrix matrix; matrix.setScale(SkIntToScalar(4), SkIntToScalar(4)); dst->transform(matrix); } #endif if (fDoFill) { if (src.cheapIsDirection(SkPath::kCCW_Direction)) { dst->reverseAddPath(src); } else { dst->addPath(src); } } else { // Seems like we can assume that a 2-point src would always result in // a convex stroke, but testing has proved otherwise. // TODO: fix the stroker to make this assumption true (without making // it slower that the work that will be done in computeConvexity()) #if 0 // this test results in a non-convex stroke :( static void test(SkCanvas* canvas) { SkPoint pts[] = { 146.333328, 192.333328, 300.333344, 293.333344 }; SkPaint paint; paint.setStrokeWidth(7); paint.setStrokeCap(SkPaint::kRound_Cap); canvas->drawLine(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY, paint); } #endif #if 0 if (2 == src.countPoints()) { dst->setIsConvex(true); } #endif }
void GetLocalBounds(const SkPath& path, const SkDrawShadowRec& rec, const SkMatrix& ctm, SkRect* bounds) { SkRect ambientBounds = path.getBounds(); SkScalar occluderZ; if (SkScalarNearlyZero(rec.fZPlaneParams.fX) && SkScalarNearlyZero(rec.fZPlaneParams.fY)) { occluderZ = rec.fZPlaneParams.fZ; } else { occluderZ = compute_z(ambientBounds.fLeft, ambientBounds.fTop, rec.fZPlaneParams); occluderZ = SkTMax(occluderZ, compute_z(ambientBounds.fRight, ambientBounds.fTop, rec.fZPlaneParams)); occluderZ = SkTMax(occluderZ, compute_z(ambientBounds.fLeft, ambientBounds.fBottom, rec.fZPlaneParams)); occluderZ = SkTMax(occluderZ, compute_z(ambientBounds.fRight, ambientBounds.fBottom, rec.fZPlaneParams)); } SkScalar ambientBlur; SkScalar spotBlur; SkScalar spotScale; SkPoint spotOffset; if (ctm.hasPerspective()) { // transform ambient and spot bounds into device space ctm.mapRect(&ambientBounds); // get ambient blur (in device space) ambientBlur = SkDrawShadowMetrics::AmbientBlurRadius(occluderZ); // get spot params (in device space) SkPoint devLightPos = SkPoint::Make(rec.fLightPos.fX, rec.fLightPos.fY); ctm.mapPoints(&devLightPos, 1); SkDrawShadowMetrics::GetSpotParams(occluderZ, devLightPos.fX, devLightPos.fY, rec.fLightPos.fZ, rec.fLightRadius, &spotBlur, &spotScale, &spotOffset); } else { SkScalar devToSrcScale = SkScalarInvert(ctm.getMinScale()); // get ambient blur (in local space) SkScalar devSpaceAmbientBlur = SkDrawShadowMetrics::AmbientBlurRadius(occluderZ); ambientBlur = devSpaceAmbientBlur*devToSrcScale; // get spot params (in local space) SkDrawShadowMetrics::GetSpotParams(occluderZ, rec.fLightPos.fX, rec.fLightPos.fY, rec.fLightPos.fZ, rec.fLightRadius, &spotBlur, &spotScale, &spotOffset); // convert spot blur to local space spotBlur *= devToSrcScale; } // in both cases, adjust ambient and spot bounds SkRect spotBounds = ambientBounds; ambientBounds.outset(ambientBlur, ambientBlur); spotBounds.fLeft *= spotScale; spotBounds.fTop *= spotScale; spotBounds.fRight *= spotScale; spotBounds.fBottom *= spotScale; spotBounds.offset(spotOffset.fX, spotOffset.fY); spotBounds.outset(spotBlur, spotBlur); // merge bounds *bounds = ambientBounds; bounds->join(spotBounds); // outset a bit to account for floating point error bounds->outset(1, 1); // if perspective, transform back to src space if (ctm.hasPerspective()) { // TODO: create tighter mapping from dev rect back to src rect SkMatrix inverse; if (ctm.invert(&inverse)) { inverse.mapRect(bounds); } } }