static bool innerPathOp(skiatest::Reporter* reporter, const SkPath& a, const SkPath& b, const SkPathOp shapeOp, const char* testName, bool expectSuccess) { #if 0 && DEBUG_SHOW_TEST_NAME showName(a, b, shapeOp); #endif SkPath out; if (!OpDebug(a, b, shapeOp, &out, expectSuccess SkDEBUGPARAMS(testName))) { SkDebugf("%s did not expect failure\n", __FUNCTION__); REPORTER_ASSERT(reporter, 0); return false; } if (!reporter->verbose()) { return true; } SkPath pathOut, scaledPathOut; SkRegion rgnA, rgnB, openClip, rgnOut; openClip.setRect(-16000, -16000, 16000, 16000); rgnA.setPath(a, openClip); rgnB.setPath(b, openClip); rgnOut.op(rgnA, rgnB, (SkRegion::Op) shapeOp); rgnOut.getBoundaryPath(&pathOut); SkMatrix scale; scaleMatrix(a, b, scale); SkRegion scaledRgnA, scaledRgnB, scaledRgnOut; SkPath scaledA, scaledB; scaledA.addPath(a, scale); scaledA.setFillType(a.getFillType()); scaledB.addPath(b, scale); scaledB.setFillType(b.getFillType()); scaledRgnA.setPath(scaledA, openClip); scaledRgnB.setPath(scaledB, openClip); scaledRgnOut.op(scaledRgnA, scaledRgnB, (SkRegion::Op) shapeOp); scaledRgnOut.getBoundaryPath(&scaledPathOut); SkBitmap bitmap; SkPath scaledOut; scaledOut.addPath(out, scale); scaledOut.setFillType(out.getFillType()); int result = comparePaths(reporter, testName, pathOut, scaledPathOut, out, scaledOut, bitmap, a, b, shapeOp, scale, expectSuccess); reporter->bumpTestCount(); return result == 0; }
bool SkHitTestPathEx(const SkPath& path, SkScalar x, SkScalar y) { bool isInverse = path.isInverseFillType(); if (path.isEmpty()) { return isInverse; } const SkRect& bounds = path.getBounds(); if (!bounds.contains(x, y)) { return isInverse; } SkPath::Iter iter(path, true); bool done = false; int w = 0; do { SkPoint pts[4]; switch (iter.next(pts, false)) { case SkPath::kMove_Verb: case SkPath::kClose_Verb: break; case SkPath::kLine_Verb: w += winding_line(pts, x, y); break; case SkPath::kQuad_Verb: w += winding_quad(pts, x, y); break; case SkPath::kCubic_Verb: w += winding_cubic(pts, x, y); break; case SkPath::kDone_Verb: done = true; break; } } while (!done); switch (path.getFillType()) { case SkPath::kEvenOdd_FillType: case SkPath::kInverseEvenOdd_FillType: w &= 1; break; default: break; } return SkToBool(w); }
static void drawAndTest(skiatest::Reporter* reporter, const SkPath& path, const SkPaint& paint, bool shouldDraw) { SkBitmap bm; bm.allocN32Pixels(DIMENSION, DIMENSION); SkASSERT(DIMENSION*4 == bm.rowBytes()); // ensure no padding on each row bm.eraseColor(SK_ColorTRANSPARENT); SkCanvas canvas(bm); SkPaint p(paint); p.setColor(SK_ColorWHITE); canvas.drawPath(path, p); size_t count = DIMENSION * DIMENSION; const SkPMColor* ptr = bm.getAddr32(0, 0); SkPMColor andValue = ~0U; SkPMColor orValue = 0; for (size_t i = 0; i < count; ++i) { SkPMColor c = ptr[i]; andValue &= c; orValue |= c; } // success means we drew everywhere or nowhere (depending on shouldDraw) bool success = shouldDraw ? (~0U == andValue) : (0 == orValue); if (!success) { const char* str; if (shouldDraw) { str = "Path expected to draw everywhere, but didn't. "; } else { str = "Path expected to draw nowhere, but did. "; } ERRORF(reporter, "%s style[%d] cap[%d] join[%d] antialias[%d]" " filltype[%d] ptcount[%d]", str, paint.getStyle(), paint.getStrokeCap(), paint.getStrokeJoin(), paint.isAntiAlias(), path.getFillType(), path.countPoints()); // uncomment this if you want to step in to see the failure // canvas.drawPath(path, p); } }
void SkPDFDevice::drawPath(const SkDraw& d, const SkPath& path, const SkPaint& paint, const SkMatrix* prePathMatrix, bool pathIsMutable) { NOT_IMPLEMENTED(prePathMatrix != NULL, true); if (paint.getPathEffect()) { // Apply the path effect to path and draw it that way. SkPath noEffectPath; paint.getFillPath(path, &noEffectPath); SkPaint noEffectPaint(paint); SkSafeUnref(noEffectPaint.setPathEffect(NULL)); drawPath(d, noEffectPath, noEffectPaint, NULL, true); return; } updateGSFromPaint(paint, false); SkPDFUtils::EmitPath(path, &fContent); SkPDFUtils::PaintPath(paint.getStyle(), path.getFillType(), &fContent); }
void SkDebugCanvas::addPathData(const SkPath& path, const char* pathName) { SkPath::RawIter iter(path); SkPath::FillType fillType = path.getFillType(); fClipStackData.appendf("%sSkPath %s;<br>", kHTML4SpaceIndent, pathName); fClipStackData.appendf("%s%s.setFillType(SkPath::%s);<br>", kHTML4SpaceIndent, pathName, gFillTypeStrs[fillType]); iter.setPath(path); uint8_t verb; SkPoint pts[4]; while ((verb = iter.next(pts)) != SkPath::kDone_Verb) { switch (verb) { case SkPath::kMove_Verb: fClipStackData.appendf("%s%s.moveTo(", kHTML4SpaceIndent, pathName); this->outputPoints(&pts[0], 1); continue; case SkPath::kLine_Verb: fClipStackData.appendf("%s%s.lineTo(", kHTML4SpaceIndent, pathName); this->outputPoints(&pts[1], 1); break; case SkPath::kQuad_Verb: fClipStackData.appendf("%s%s.quadTo(", kHTML4SpaceIndent, pathName); this->outputPoints(&pts[1], 2); break; case SkPath::kConic_Verb: fClipStackData.appendf("%s%s.conicTo(", kHTML4SpaceIndent, pathName); this->outputConicPoints(&pts[1], iter.conicWeight()); break; case SkPath::kCubic_Verb: fClipStackData.appendf("%s%s.cubicTo(", kHTML4SpaceIndent, pathName); this->outputPoints(&pts[1], 3); break; case SkPath::kClose_Verb: fClipStackData.appendf("%s%s.close();<br>", kHTML4SpaceIndent, pathName); break; default: SkDEBUGFAIL("bad verb"); return; } } }
PassRefPtr<JSONObject> LoggingCanvas::objectForSkPath(const SkPath& path) { RefPtr<JSONObject> pathItem = JSONObject::create(); pathItem->setString("fillType", fillTypeName(path.getFillType())); pathItem->setString("convexity", convexityName(path.getConvexity())); pathItem->setBoolean("isRect", path.isRect(0)); SkPath::Iter iter(path, false); SkPoint points[4]; RefPtr<JSONArray> pathPointsArray = JSONArray::create(); for (SkPath::Verb verb = iter.next(points, false); verb != SkPath::kDone_Verb; verb = iter.next(points, false)) { VerbParams verbParams = segmentParams(verb); RefPtr<JSONObject> pathPointItem = JSONObject::create(); pathPointItem->setString("verb", verbParams.name); ASSERT(verbParams.pointCount + verbParams.pointOffset <= WTF_ARRAY_LENGTH(points)); pathPointItem->setArray("points", arrayForSkPoints(verbParams.pointCount, points + verbParams.pointOffset)); if (SkPath::kConic_Verb == verb) pathPointItem->setNumber("conicWeight", iter.conicWeight()); pathPointsArray->pushObject(pathPointItem); } pathItem->setArray("pathPoints", pathPointsArray); pathItem->setObject("bounds", objectForSkRect(path.getBounds())); return pathItem.release(); }
static void dontFailOne(skiatest::Reporter* reporter, int index) { SkPath path; int f = (int) (index % finitePtsCount); int g = (int) ((f + 1) % finitePtsCount); switch (index % 11) { case 0: path.lineTo(finitePts[f]); break; case 1: path.quadTo(finitePts[f], finitePts[f]); break; case 2: path.quadTo(finitePts[f], finitePts[g]); break; case 3: path.quadTo(finitePts[g], finitePts[f]); break; case 4: path.cubicTo(finitePts[f], finitePts[f], finitePts[f]); break; case 5: path.cubicTo(finitePts[f], finitePts[f], finitePts[g]); break; case 6: path.cubicTo(finitePts[f], finitePts[g], finitePts[f]); break; case 7: path.cubicTo(finitePts[f], finitePts[g], finitePts[g]); break; case 8: path.cubicTo(finitePts[g], finitePts[f], finitePts[f]); break; case 9: path.cubicTo(finitePts[g], finitePts[f], finitePts[g]); break; case 10: path.moveTo(finitePts[f]); break; } SkPath result; result.setFillType(SkPath::kWinding_FillType); bool success = Simplify(path, &result); REPORTER_ASSERT(reporter, success); REPORTER_ASSERT(reporter, result.getFillType() != SkPath::kWinding_FillType); reporter->bumpTestCount(); }
bool GrDefaultPathRenderer::internalDrawPath(GrDrawContext* drawContext, const GrPaint& paint, const GrUserStencilSettings& userStencilSettings, const GrClip& clip, const SkMatrix& viewMatrix, const GrShape& shape, bool stencilOnly) { SkPath path; shape.asPath(&path); SkScalar hairlineCoverage; uint8_t newCoverage = 0xff; bool isHairline = false; if (IsStrokeHairlineOrEquivalent(shape.style(), viewMatrix, &hairlineCoverage)) { newCoverage = SkScalarRoundToInt(hairlineCoverage * 0xff); isHairline = true; } else { SkASSERT(shape.style().isSimpleFill()); } int passCount = 0; const GrUserStencilSettings* passes[3]; GrDrawFace drawFace[3]; bool reverse = false; bool lastPassIsBounds; if (isHairline) { passCount = 1; if (stencilOnly) { passes[0] = &gDirectToStencil; } else { passes[0] = &userStencilSettings; } lastPassIsBounds = false; drawFace[0] = GrDrawFace::kBoth; } else { if (single_pass_shape(shape)) { passCount = 1; if (stencilOnly) { passes[0] = &gDirectToStencil; } else { passes[0] = &userStencilSettings; } drawFace[0] = GrDrawFace::kBoth; 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] = GrDrawFace::kBoth; 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] = GrDrawFace::kBoth; } 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] = GrDrawFace::kCW; drawFace[1] = GrDrawFace::kCCW; passCount = 3; } if (stencilOnly) { lastPassIsBounds = false; --passCount; } else { lastPassIsBounds = true; drawFace[passCount-1] = GrDrawFace::kBoth; 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, drawContext->width(), drawContext->height(), viewMatrix, &devBounds); for (int p = 0; p < passCount; ++p) { if (lastPassIsBounds && (p == passCount-1)) { SkRect bounds; SkMatrix localMatrix = SkMatrix::I(); if (reverse) { // 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; SkAutoTUnref<GrDrawBatch> batch( GrRectBatchFactory::CreateNonAAFill(paint.getColor(), viewM, bounds, nullptr, &localMatrix)); SkASSERT(GrDrawFace::kBoth == drawFace[p]); GrPipelineBuilder pipelineBuilder(paint, drawContext->mustUseHWAA(paint)); pipelineBuilder.setDrawFace(drawFace[p]); pipelineBuilder.setUserStencil(passes[p]); drawContext->drawBatch(pipelineBuilder, clip, batch); } else { SkAutoTUnref<GrDrawBatch> batch(new DefaultPathBatch(paint.getColor(), path, srcSpaceTol, newCoverage, viewMatrix, isHairline, devBounds)); GrPipelineBuilder pipelineBuilder(paint, drawContext->mustUseHWAA(paint)); pipelineBuilder.setDrawFace(drawFace[p]); pipelineBuilder.setUserStencil(passes[p]); if (passCount > 1) { pipelineBuilder.setDisableColorXPFactory(); } drawContext->drawBatch(pipelineBuilder, clip, batch); } } return true; }
bool GrStencilAndCoverPathRenderer::onDrawPath(GrDrawTarget* target, GrPipelineBuilder* pipelineBuilder, GrColor color, const SkMatrix& viewMatrix, const SkPath& path, const GrStrokeInfo& stroke, bool antiAlias) { SkASSERT(!antiAlias); SkASSERT(!stroke.getStrokeRec().isHairlineStyle()); SkASSERT(!stroke.isDashed()); SkASSERT(pipelineBuilder->getStencil().isDisabled()); SkAutoTUnref<GrPath> p(get_gr_path(fGpu, path, stroke.getStrokeRec())); if (path.isInverseFillType()) { 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); pipelineBuilder->setStencil(kInvertedStencilPass); // fake inverse with a stencil and cover SkAutoTUnref<GrPathProcessor> pp(GrPathProcessor::Create(GrColor_WHITE, viewMatrix)); target->stencilPath(pipelineBuilder, pp, p, convert_skpath_filltype(path.getFillType())); SkMatrix invert = SkMatrix::I(); SkRect bounds = SkRect::MakeLTRB(0, 0, SkIntToScalar(pipelineBuilder->getRenderTarget()->width()), SkIntToScalar(pipelineBuilder->getRenderTarget()->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; target->drawRect(pipelineBuilder, color, viewM, bounds, NULL, &invert); } else { GR_STATIC_CONST_SAME_STENCIL(kStencilPass, kZero_StencilOp, kZero_StencilOp, kNotEqual_StencilFunc, 0xffff, 0x0000, 0xffff); pipelineBuilder->setStencil(kStencilPass); SkAutoTUnref<GrPathProcessor> pp(GrPathProcessor::Create(color, viewMatrix)); target->drawPath(pipelineBuilder, pp, p, convert_skpath_filltype(path.getFillType())); } pipelineBuilder->stencil()->setDisabled(); return true; }
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; }
bool GrStencilAndCoverPathRenderer::onDrawPath(const SkPath& path, const SkStrokeRec& stroke, GrDrawTarget* target, bool antiAlias) { SkASSERT(!antiAlias); SkASSERT(!stroke.isHairlineStyle()); GrDrawState* drawState = target->drawState(); SkASSERT(drawState->getStencil().isDisabled()); SkAutoTUnref<GrPath> p(fGpu->createPath(path)); SkPath::FillType nonInvertedFill = SkPath::ConvertToNonInverseFillType(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 SkRect bounds = p->getBounds(); SkScalar bloat = drawState->getViewMatrix().getMaxStretch() * SK_ScalarHalf; GrDrawState::AutoViewMatrixRestore avmr; 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 { avmr.setIdentity(drawState); bloat = 0; } *drawState->stencil() = kInvertedStencilPass; } bounds.outset(bloat, bloat); target->drawSimpleRect(bounds, NULL); target->drawState()->stencil()->setDisabled(); return true; }
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->getStrokeRec().isHairlineStyle()) { stroke.writable()->getStrokeRecPtr()->setHairlineStyle(); } } const bool isHairline = stroke->getStrokeRec().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->getStrokeRec())) { 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->drawRect(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; }
bool GrStencilAndCoverPathRenderer::onDrawPath(const SkPath& path, const SkStrokeRec& stroke, GrDrawTarget* target, bool antiAlias) { SkASSERT(!antiAlias); SkASSERT(!stroke.isHairlineStyle()); GrDrawState* drawState = target->drawState(); SkASSERT(drawState->getStencil().isDisabled()); SkAutoTUnref<GrPath> p(get_gr_path(fGpu, path, stroke)); if (path.isInverseFillType()) { 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); drawState->setStencil(kInvertedStencilPass); // fake inverse with a stencil and cover target->stencilPath(p, convert_skpath_filltype(path.getFillType())); GrDrawState::AutoViewMatrixRestore avmr; SkRect bounds = SkRect::MakeLTRB(0, 0, SkIntToScalar(drawState->getRenderTarget()->width()), SkIntToScalar(drawState->getRenderTarget()->height())); SkMatrix vmi; // 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. SkScalar bloat = drawState->getViewMatrix().getMaxScale() * SK_ScalarHalf; bounds.outset(bloat, bloat); } else { avmr.setIdentity(drawState); } target->drawSimpleRect(bounds); } else { GR_STATIC_CONST_SAME_STENCIL(kStencilPass, kZero_StencilOp, kZero_StencilOp, kNotEqual_StencilFunc, 0xffff, 0x0000, 0xffff); drawState->setStencil(kStencilPass); target->drawPath(p, convert_skpath_filltype(path.getFillType())); } target->drawState()->stencil()->setDisabled(); return true; }
Json::Value SkJSONCanvas::makePath(const SkPath& path) { Json::Value result(Json::objectValue); switch (path.getFillType()) { case SkPath::kWinding_FillType: result[SKJSONCANVAS_ATTRIBUTE_FILLTYPE] = SKJSONCANVAS_FILLTYPE_WINDING; break; case SkPath::kEvenOdd_FillType: result[SKJSONCANVAS_ATTRIBUTE_FILLTYPE] = SKJSONCANVAS_FILLTYPE_EVENODD; break; case SkPath::kInverseWinding_FillType: result[SKJSONCANVAS_ATTRIBUTE_FILLTYPE] = SKJSONCANVAS_FILLTYPE_INVERSEWINDING; break; case SkPath::kInverseEvenOdd_FillType: result[SKJSONCANVAS_ATTRIBUTE_FILLTYPE] = SKJSONCANVAS_FILLTYPE_INVERSEEVENODD; break; } Json::Value verbs(Json::arrayValue); SkPath::Iter iter(path, false); SkPoint pts[4]; SkPath::Verb verb; while ((verb = iter.next(pts)) != SkPath::kDone_Verb) { switch (verb) { case SkPath::kLine_Verb: { Json::Value line(Json::objectValue); line[SKJSONCANVAS_VERB_LINE] = this->makePoint(pts[1]); verbs.append(line); break; } case SkPath::kQuad_Verb: { Json::Value quad(Json::objectValue); Json::Value coords(Json::arrayValue); coords.append(this->makePoint(pts[1])); coords.append(this->makePoint(pts[2])); quad[SKJSONCANVAS_VERB_QUAD] = coords; verbs.append(quad); break; } case SkPath::kCubic_Verb: { Json::Value cubic(Json::objectValue); Json::Value coords(Json::arrayValue); coords.append(this->makePoint(pts[1])); coords.append(this->makePoint(pts[2])); coords.append(this->makePoint(pts[3])); cubic[SKJSONCANVAS_VERB_CUBIC] = coords; verbs.append(cubic); break; } case SkPath::kConic_Verb: { Json::Value conic(Json::objectValue); Json::Value coords(Json::arrayValue); coords.append(this->makePoint(pts[1])); coords.append(this->makePoint(pts[2])); coords.append(Json::Value(iter.conicWeight())); conic[SKJSONCANVAS_VERB_CONIC] = coords; verbs.append(conic); break; } case SkPath::kMove_Verb: { Json::Value move(Json::objectValue); move[SKJSONCANVAS_VERB_MOVE] = this->makePoint(pts[0]); verbs.append(move); break; } case SkPath::kClose_Verb: verbs.append(Json::Value(SKJSONCANVAS_VERB_CLOSE)); break; case SkPath::kDone_Verb: break; } } result[SKJSONCANVAS_ATTRIBUTE_VERBS] = verbs; return result; }
bool GrDefaultPathRenderer::internalDrawPath(GrRenderTargetContext* renderTargetContext, GrPaint&& paint, GrAAType aaType, const GrUserStencilSettings& userStencilSettings, const GrClip& clip, const SkMatrix& viewMatrix, const GrShape& shape, bool stencilOnly) { auto context = renderTargetContext->surfPriv().getContext(); SkASSERT(GrAAType::kCoverage != aaType); SkPath path; shape.asPath(&path); SkScalar hairlineCoverage; uint8_t newCoverage = 0xff; bool isHairline = false; if (IsStrokeHairlineOrEquivalent(shape.style(), viewMatrix, &hairlineCoverage)) { newCoverage = SkScalarRoundToInt(hairlineCoverage * 0xff); isHairline = true; } else { SkASSERT(shape.style().isSimpleFill()); } int passCount = 0; const GrUserStencilSettings* passes[2]; bool reverse = false; bool lastPassIsBounds; if (isHairline) { passCount = 1; if (stencilOnly) { passes[0] = &gDirectToStencil; } else { passes[0] = &userStencilSettings; } lastPassIsBounds = false; } else { if (single_pass_shape(shape)) { passCount = 1; if (stencilOnly) { passes[0] = &gDirectToStencil; } else { passes[0] = &userStencilSettings; } 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; } } break; case SkPath::kInverseWinding_FillType: reverse = true; // fallthrough case SkPath::kWinding_FillType: passes[0] = &gWindStencilPass; passCount = 2; if (stencilOnly) { lastPassIsBounds = false; --passCount; } else { lastPassIsBounds = true; 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, renderTargetContext->asRenderTargetProxy()->worstCaseWidth(), renderTargetContext->asRenderTargetProxy()->worstCaseHeight(), viewMatrix, &devBounds); for (int p = 0; p < passCount; ++p) { if (lastPassIsBounds && (p == passCount-1)) { SkRect bounds; SkMatrix localMatrix = SkMatrix::I(); if (reverse) { // 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; // This is a non-coverage aa rect op since we assert aaType != kCoverage at the start assert_alive(paint); renderTargetContext->priv().stencilRect(clip, passes[p], std::move(paint), GrAA(aaType == GrAAType::kMSAA), viewM, bounds, &localMatrix); } else { bool stencilPass = stencilOnly || passCount > 1; std::unique_ptr<GrDrawOp> op; if (stencilPass) { GrPaint stencilPaint; stencilPaint.setXPFactory(GrDisableColorXPFactory::Get()); op = DefaultPathOp::Make(context, std::move(stencilPaint), path, srcSpaceTol, newCoverage, viewMatrix, isHairline, aaType, devBounds, passes[p]); } else { assert_alive(paint); op = DefaultPathOp::Make(context, std::move(paint), path, srcSpaceTol, newCoverage, viewMatrix, isHairline, aaType, devBounds, passes[p]); } renderTargetContext->addDrawOp(clip, std::move(op)); } } return true; }
// clipRect has not been shifted up void sk_fill_path(const SkPath& path, const SkIRect& clipRect, SkBlitter* blitter, int start_y, int stop_y, int shiftEdgesUp, bool pathContainedInClip) { SkASSERT(blitter); SkIRect shiftedClip = clipRect; shiftedClip.fLeft = SkLeftShift(shiftedClip.fLeft, shiftEdgesUp); shiftedClip.fRight = SkLeftShift(shiftedClip.fRight, shiftEdgesUp); shiftedClip.fTop = SkLeftShift(shiftedClip.fTop, shiftEdgesUp); shiftedClip.fBottom = SkLeftShift(shiftedClip.fBottom, shiftEdgesUp); SkEdgeBuilder builder; int count = builder.build_edges(path, &shiftedClip, shiftEdgesUp, pathContainedInClip); SkEdge** list = builder.edgeList(); if (0 == count) { if (path.isInverseFillType()) { /* * Since we are in inverse-fill, our caller has already drawn above * our top (start_y) and will draw below our bottom (stop_y). Thus * we need to restrict our drawing to the intersection of the clip * and those two limits. */ SkIRect rect = clipRect; if (rect.fTop < start_y) { rect.fTop = start_y; } if (rect.fBottom > stop_y) { rect.fBottom = stop_y; } if (!rect.isEmpty()) { blitter->blitRect(rect.fLeft << shiftEdgesUp, rect.fTop << shiftEdgesUp, rect.width() << shiftEdgesUp, rect.height() << shiftEdgesUp); } } return; } SkEdge headEdge, tailEdge, *last; // this returns the first and last edge after they're sorted into a dlink list SkEdge* edge = sort_edges(list, count, &last); headEdge.fPrev = nullptr; headEdge.fNext = edge; headEdge.fFirstY = kEDGE_HEAD_Y; headEdge.fX = SK_MinS32; edge->fPrev = &headEdge; tailEdge.fPrev = last; tailEdge.fNext = nullptr; tailEdge.fFirstY = kEDGE_TAIL_Y; last->fNext = &tailEdge; // now edge is the head of the sorted linklist start_y = SkLeftShift(start_y, shiftEdgesUp); stop_y = SkLeftShift(stop_y, shiftEdgesUp); if (!pathContainedInClip && start_y < shiftedClip.fTop) { start_y = shiftedClip.fTop; } if (!pathContainedInClip && stop_y > shiftedClip.fBottom) { stop_y = shiftedClip.fBottom; } InverseBlitter ib; PrePostProc proc = nullptr; if (path.isInverseFillType()) { ib.setBlitter(blitter, clipRect, shiftEdgesUp); blitter = &ib; proc = PrePostInverseBlitterProc; } // count >= 2 is required as the convex walker does not handle missing right edges if (path.isConvex() && (nullptr == proc) && count >= 2) { walk_simple_edges(&headEdge, blitter, start_y, stop_y); } else { walk_edges(&headEdge, path.getFillType(), blitter, start_y, stop_y, proc, shiftedClip.right()); } }
bool GrDefaultPathRenderer::internalDrawPath(const SkPath& path, const SkStrokeRec& origStroke, GrDrawTarget* target, bool stencilOnly) { SkMatrix viewM = target->getDrawState().getViewMatrix(); SkTCopyOnFirstWrite<SkStrokeRec> stroke(origStroke); SkScalar hairlineCoverage; if (IsStrokeHairlineOrEquivalent(*stroke, target->getDrawState().getViewMatrix(), &hairlineCoverage)) { uint8_t newCoverage = SkScalarRoundToInt(hairlineCoverage * target->getDrawState().getCoverage()); target->drawState()->setCoverage(newCoverage); if (!stroke->isHairlineStyle()) { stroke.writable()->setHairlineStyle(); } } SkScalar tol = SK_Scalar1; tol = GrPathUtils::scaleToleranceToSrc(tol, viewM, path.getBounds()); int vertexCnt; int indexCnt; GrPrimitiveType primType; GrDrawTarget::AutoReleaseGeometry arg; if (!this->createGeom(path, *stroke, tol, target, &primType, &vertexCnt, &indexCnt, &arg)) { return false; } SkASSERT(NULL != target); GrDrawTarget::AutoStateRestore asr(target, GrDrawTarget::kPreserve_ASRInit); GrDrawState* drawState = target->drawState(); bool colorWritesWereDisabled = drawState->isColorWriteDisabled(); // face culling doesn't make sense here SkASSERT(GrDrawState::kBoth_DrawFace == drawState->getDrawFace()); int passCount = 0; const GrStencilSettings* passes[3]; GrDrawState::DrawFace drawFace[3]; bool reverse = false; bool lastPassIsBounds; if (stroke->isHairlineStyle()) { passCount = 1; if (stencilOnly) { passes[0] = &gDirectToStencil; } else { passes[0] = NULL; } lastPassIsBounds = false; drawFace[0] = GrDrawState::kBoth_DrawFace; } else { if (single_pass_path(path, *stroke)) { passCount = 1; if (stencilOnly) { passes[0] = &gDirectToStencil; } else { passes[0] = NULL; } drawFace[0] = GrDrawState::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] = GrDrawState::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] = GrDrawState::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] = GrDrawState::kCW_DrawFace; drawFace[1] = GrDrawState::kCCW_DrawFace; passCount = 3; } if (stencilOnly) { lastPassIsBounds = false; --passCount; } else { lastPassIsBounds = true; drawFace[passCount-1] = GrDrawState::kBoth_DrawFace; if (reverse) { passes[passCount-1] = &gInvWindColorPass; } else { passes[passCount-1] = &gWindColorPass; } } break; default: SkDEBUGFAIL("Unknown path fFill!"); return false; } } } SkRect devBounds; GetPathDevBounds(path, drawState->getRenderTarget(), viewM, &devBounds); for (int p = 0; p < passCount; ++p) { drawState->setDrawFace(drawFace[p]); if (NULL != passes[p]) { *drawState->stencil() = *passes[p]; } if (lastPassIsBounds && (p == passCount-1)) { if (!colorWritesWereDisabled) { drawState->disableState(GrDrawState::kNoColorWrites_StateBit); } SkRect bounds; GrDrawState::AutoViewMatrixRestore avmr; if (reverse) { SkASSERT(NULL != drawState->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 (!drawState->getViewMatrix().hasPerspective() && drawState->getViewInverse(&vmi)) { vmi.mapRect(&bounds); } else { avmr.setIdentity(drawState); } } else { bounds = path.getBounds(); } GrDrawTarget::AutoGeometryAndStatePush agasp(target, GrDrawTarget::kPreserve_ASRInit); target->drawSimpleRect(bounds, NULL); } else { if (passCount > 1) { drawState->enableState(GrDrawState::kNoColorWrites_StateBit); } if (indexCnt) { target->drawIndexed(primType, 0, 0, vertexCnt, indexCnt, &devBounds); } else { target->drawNonIndexed(primType, 0, vertexCnt, &devBounds); } } } return true; }