void CanvasRenderingContext2DState::clipPath(const SkPath& path, AntiAliasingMode antiAliasingMode) { m_clipList.clipPath(path, antiAliasingMode, affineTransformToSkMatrix(m_transform)); m_hasClip = true; if (!path.isRect(0)) m_hasComplexClip = true; }
void SkSVGDevice::AutoElement::addClipResources(const SkDraw& draw, Resources* resources) { SkASSERT(!draw.fClipStack->isWideOpen()); SkPath clipPath; (void) draw.fClipStack->asPath(&clipPath); SkString clipID = fResourceBucket->addClip(); const char* clipRule = clipPath.getFillType() == SkPath::kEvenOdd_FillType ? "evenodd" : "nonzero"; { // clipPath is in device space, but since we're only pushing transform attributes // to the leaf nodes, so are all our elements => SVG userSpaceOnUse == device space. AutoElement clipPathElement("clipPath", fWriter); clipPathElement.addAttribute("id", clipID); SkRect clipRect = SkRect::MakeEmpty(); if (clipPath.isEmpty() || clipPath.isRect(&clipRect)) { AutoElement rectElement("rect", fWriter); rectElement.addRectAttributes(clipRect); rectElement.addAttribute("clip-rule", clipRule); } else { AutoElement pathElement("path", fWriter); pathElement.addPathAttributes(clipPath); pathElement.addAttribute("clip-rule", clipRule); } } resources->fClip.printf("url(#%s)", clipID.c_str()); }
void draw(SkCanvas* canvas) { SkPaint paint; paint.setAntiAlias(true); for (auto xradius : { 0, 7, 13, 20 } ) { for (auto yradius : { 0, 9, 18, 40 } ) { SkPath path; path.addRoundRect({10, 10, 36, 46}, xradius, yradius); paint.setColor(path.isRect(nullptr) ? SK_ColorRED : path.isOval(nullptr) ? SK_ColorBLUE : path.isConvex() ? SK_ColorGRAY : SK_ColorGREEN); canvas->drawPath(path, paint); canvas->translate(64, 0); } canvas->translate(-256, 64); } }
void OpaqueRegionSkia::didDrawPath(const PlatformContextSkia* context, const SkPath& path, const SkPaint& paint) { SkRect rect; if (path.isRect(&rect)) { didDrawRect(context, rect, paint, 0); return; } bool fillsBounds = false; if (!paint.canComputeFastBounds()) didDrawUnbounded(paint); else { rect = paint.computeFastBounds(path.getBounds(), &rect); didDraw(context, rect, paint, 0, fillsBounds, FillOrStroke); } }
void OpaqueRegionSkia::didDrawPath(const PlatformContextSkia* context, const AffineTransform& transform, const SkPath& path, const SkPaint& paint) { SkRect rect; if (path.isRect(&rect)) { didDrawRect(context, transform, rect, paint, 0); return; } bool opaque = paintIsOpaque(paint); bool fillsBounds = false; if (!paint.canComputeFastBounds()) didDrawUnbounded(paint, opaque); else { rect = paint.computeFastBounds(path.getBounds(), &rect); didDraw(context, transform, rect, paint, opaque, fillsBounds); } }
bool SkPictureRecord::clipPath(const SkPath& path, SkRegion::Op op, bool doAA) { SkRect r; if (!path.isInverseFillType() && path.isRect(&r)) { return this->clipRect(r, op, doAA); } addDraw(CLIP_PATH); addPath(path); addInt(ClipParams_pack(op, doAA)); recordRestoreOffsetPlaceholder(op); validate(); if (fRecordFlags & SkPicture::kUsePathBoundsForClip_RecordingFlag) { return this->INHERITED::clipRect(path.getBounds(), op, doAA); } else { return this->INHERITED::clipPath(path, op, doAA); } }
void SkClipStack::Element::initPath(int saveCount, const SkPath& path, SkRegion::Op op, bool doAA) { if (!path.isInverseFillType()) { SkRect r; if (path.isRect(&r)) { this->initRect(saveCount, r, op, doAA); return; } SkRect ovalRect; if (path.isOval(&ovalRect)) { SkRRect rrect; rrect.setOval(ovalRect); this->initRRect(saveCount, rrect, op, doAA); return; } } fPath.set(path); fPath.get()->setIsVolatile(true); fType = kPath_Type; this->initCommon(saveCount, op, doAA); }
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 int countNestedRects(const SkPath& path, SkRect rects[2]) { if (path.isNestedRects(rects)) { return 2; } return path.isRect(&rects[0]); }
DEF_TEST(PathOpsBuilder, reporter) { SkOpBuilder builder; SkPath result; REPORTER_ASSERT(reporter, builder.resolve(&result)); REPORTER_ASSERT(reporter, result.isEmpty()); builder.add(result, kDifference_SkPathOp); REPORTER_ASSERT(reporter, builder.resolve(&result)); REPORTER_ASSERT(reporter, result.isEmpty()); builder.add(result, kUnion_SkPathOp); REPORTER_ASSERT(reporter, builder.resolve(&result)); REPORTER_ASSERT(reporter, result.isEmpty()); SkPath rectPath; rectPath.setFillType(SkPath::kEvenOdd_FillType); rectPath.addRect(0, 1, 2, 3, SkPath::kCW_Direction); builder.add(rectPath, kUnion_SkPathOp); REPORTER_ASSERT(reporter, builder.resolve(&result)); bool closed; SkPath::Direction dir; REPORTER_ASSERT(reporter, result.isRect(nullptr, &closed, &dir)); REPORTER_ASSERT(reporter, closed); REPORTER_ASSERT(reporter, dir == SkPath::kCCW_Direction); int pixelDiff = comparePaths(reporter, __FUNCTION__, rectPath, result); REPORTER_ASSERT(reporter, pixelDiff == 0); rectPath.reset(); rectPath.setFillType(SkPath::kEvenOdd_FillType); rectPath.addRect(0, 1, 2, 3, SkPath::kCCW_Direction); builder.add(rectPath, kUnion_SkPathOp); REPORTER_ASSERT(reporter, builder.resolve(&result)); REPORTER_ASSERT(reporter, result.isRect(nullptr, &closed, &dir)); REPORTER_ASSERT(reporter, closed); REPORTER_ASSERT(reporter, dir == SkPath::kCCW_Direction); REPORTER_ASSERT(reporter, rectPath == result); builder.add(rectPath, kDifference_SkPathOp); REPORTER_ASSERT(reporter, builder.resolve(&result)); REPORTER_ASSERT(reporter, result.isEmpty()); SkPath rect2, rect3; rect2.addRect(2, 1, 4, 3, SkPath::kCW_Direction); rect3.addRect(4, 1, 5, 3, SkPath::kCCW_Direction); builder.add(rectPath, kUnion_SkPathOp); builder.add(rect2, kUnion_SkPathOp); builder.add(rect3, kUnion_SkPathOp); REPORTER_ASSERT(reporter, builder.resolve(&result)); REPORTER_ASSERT(reporter, result.isRect(nullptr, &closed, &dir)); REPORTER_ASSERT(reporter, closed); SkRect expected; expected.set(0, 1, 5, 3); REPORTER_ASSERT(reporter, result.getBounds() == expected); SkPath circle1, circle2, circle3; circle1.addCircle(5, 6, 4, SkPath::kCW_Direction); circle2.addCircle(7, 4, 8, SkPath::kCCW_Direction); circle3.addCircle(6, 5, 6, SkPath::kCW_Direction); SkPath opCompare; Op(circle1, circle2, kUnion_SkPathOp, &opCompare); Op(opCompare, circle3, kDifference_SkPathOp, &opCompare); builder.add(circle1, kUnion_SkPathOp); builder.add(circle2, kUnion_SkPathOp); builder.add(circle3, kDifference_SkPathOp); REPORTER_ASSERT(reporter, builder.resolve(&result)); pixelDiff = comparePaths(reporter, __FUNCTION__, opCompare, result); REPORTER_ASSERT(reporter, pixelDiff == 0); }
void SkStroke::strokePath(const SkPath& src, SkPath* dst) const { SkASSERT(&src != NULL && dst != NULL); SkScalar radius = SkScalarHalf(fWidth); AutoTmpPath tmp(src, &dst); if (radius <= 0) { return; } // If src is really a rect, call our specialty strokeRect() method { bool isClosed; SkPath::Direction dir; if (src.isRect(&isClosed, &dir) && isClosed) { this->strokeRect(src.getBounds(), dst, dir); // our answer should preserve the inverseness of the src if (src.isInverseFillType()) { SkASSERT(!dst->isInverseFillType()); dst->toggleInverseFillType(); } return; } } SkAutoConicToQuads converter; const SkScalar conicTol = SK_Scalar1 / 4; SkPathStroker stroker(src, radius, fMiterLimit, this->getCap(), this->getJoin()); SkPath::Iter iter(src, false); SkPath::Verb lastSegment = SkPath::kMove_Verb; for (;;) { SkPoint pts[4]; switch (iter.next(pts, false)) { case SkPath::kMove_Verb: stroker.moveTo(pts[0]); break; case SkPath::kLine_Verb: stroker.lineTo(pts[1]); lastSegment = SkPath::kLine_Verb; break; case SkPath::kQuad_Verb: stroker.quadTo(pts[1], pts[2]); lastSegment = SkPath::kQuad_Verb; break; case SkPath::kConic_Verb: { // todo: if we had maxcurvature for conics, perhaps we should // natively extrude the conic instead of converting to quads. const SkPoint* quadPts = converter.computeQuads(pts, iter.conicWeight(), conicTol); for (int i = 0; i < converter.countQuads(); ++i) { stroker.quadTo(quadPts[1], quadPts[2]); quadPts += 2; } lastSegment = SkPath::kQuad_Verb; } break; case SkPath::kCubic_Verb: stroker.cubicTo(pts[1], pts[2], pts[3]); lastSegment = SkPath::kCubic_Verb; break; case SkPath::kClose_Verb: stroker.close(lastSegment == SkPath::kLine_Verb); break; case SkPath::kDone_Verb: goto DONE; } } DONE: stroker.done(dst, lastSegment == SkPath::kLine_Verb); 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 }
// Simple isRect test is inline TestPath, below. // test_isRect provides more extensive testing. static void test_isRect(skiatest::Reporter* reporter) { // passing tests (all moveTo / lineTo... SkPoint r1[] = {{0, 0}, {1, 0}, {1, 1}, {0, 1}}; SkPoint r2[] = {{1, 0}, {1, 1}, {0, 1}, {0, 0}}; SkPoint r3[] = {{1, 1}, {0, 1}, {0, 0}, {1, 0}}; SkPoint r4[] = {{0, 1}, {0, 0}, {1, 0}, {1, 1}}; SkPoint r5[] = {{0, 0}, {0, 1}, {1, 1}, {1, 0}}; SkPoint r6[] = {{0, 1}, {1, 1}, {1, 0}, {0, 0}}; SkPoint r7[] = {{1, 1}, {1, 0}, {0, 0}, {0, 1}}; SkPoint r8[] = {{1, 0}, {0, 0}, {0, 1}, {1, 1}}; SkPoint r9[] = {{0, 1}, {1, 1}, {1, 0}, {0, 0}}; SkPoint ra[] = {{0, 0}, {0, .5f}, {0, 1}, {.5f, 1}, {1, 1}, {1, .5f}, {1, 0}, {.5f, 0}}; SkPoint rb[] = {{0, 0}, {.5f, 0}, {1, 0}, {1, .5f}, {1, 1}, {.5f, 1}, {0, 1}, {0, .5f}}; SkPoint rc[] = {{0, 0}, {1, 0}, {1, 1}, {0, 1}, {0, 0}}; SkPoint rd[] = {{0, 0}, {0, 1}, {1, 1}, {1, 0}, {0, 0}}; SkPoint re[] = {{0, 0}, {1, 0}, {1, 0}, {1, 1}, {0, 1}}; // failing tests SkPoint f1[] = {{0, 0}, {1, 0}, {1, 1}}; // too few points SkPoint f2[] = {{0, 0}, {1, 1}, {0, 1}, {1, 0}}; // diagonal SkPoint f3[] = {{0, 0}, {1, 0}, {1, 1}, {0, 1}, {0, 0}, {1, 0}}; // wraps SkPoint f4[] = {{0, 0}, {1, 0}, {0, 0}, {1, 0}, {1, 1}, {0, 1}}; // backs up SkPoint f5[] = {{0, 0}, {1, 0}, {1, 1}, {2, 0}}; // end overshoots SkPoint f6[] = {{0, 0}, {1, 0}, {1, 1}, {0, 1}, {0, 2}}; // end overshoots SkPoint f7[] = {{0, 0}, {1, 0}, {1, 1}, {0, 2}}; // end overshoots SkPoint f8[] = {{0, 0}, {1, 0}, {1, 1}, {1, 0}}; // 'L' // failing, no close SkPoint c1[] = {{0, 0}, {1, 0}, {1, 1}, {0, 1}}; // close doesn't match SkPoint c2[] = {{0, 0}, {1, 0}, {1, 2}, {0, 2}, {0, 1}}; // ditto size_t testLen[] = { sizeof(r1), sizeof(r2), sizeof(r3), sizeof(r4), sizeof(r5), sizeof(r6), sizeof(r7), sizeof(r8), sizeof(r9), sizeof(ra), sizeof(rb), sizeof(rc), sizeof(rd), sizeof(re), sizeof(f1), sizeof(f2), sizeof(f3), sizeof(f4), sizeof(f5), sizeof(f6), sizeof(f7), sizeof(f8), sizeof(c1), sizeof(c2) }; SkPoint* tests[] = { r1, r2, r3, r4, r5, r6, r7, r8, r9, ra, rb, rc, rd, re, f1, f2, f3, f4, f5, f6, f7, f8, c1, c2 }; SkPoint* lastPass = re; SkPoint* lastClose = f8; bool fail = false; bool close = true; const size_t testCount = sizeof(tests) / sizeof(tests[0]); size_t index; for (size_t testIndex = 0; testIndex < testCount; ++testIndex) { SkPath path; path.moveTo(tests[testIndex][0].fX, tests[testIndex][0].fY); for (index = 1; index < testLen[testIndex] / sizeof(SkPoint); ++index) { path.lineTo(tests[testIndex][index].fX, tests[testIndex][index].fY); } if (close) { path.close(); } REPORTER_ASSERT(reporter, fail ^ path.isRect(0)); if (tests[testIndex] == lastPass) { fail = true; } if (tests[testIndex] == lastClose) { close = false; } } // fail, close then line SkPath path1; path1.moveTo(r1[0].fX, r1[0].fY); for (index = 1; index < testLen[0] / sizeof(SkPoint); ++index) { path1.lineTo(r1[index].fX, r1[index].fY); } path1.close(); path1.lineTo(1, 0); REPORTER_ASSERT(reporter, fail ^ path1.isRect(0)); // fail, move in the middle path1.reset(); path1.moveTo(r1[0].fX, r1[0].fY); for (index = 1; index < testLen[0] / sizeof(SkPoint); ++index) { if (index == 2) { path1.moveTo(1, .5f); } path1.lineTo(r1[index].fX, r1[index].fY); } path1.close(); REPORTER_ASSERT(reporter, fail ^ path1.isRect(0)); // fail, move on the edge path1.reset(); for (index = 1; index < testLen[0] / sizeof(SkPoint); ++index) { path1.moveTo(r1[index - 1].fX, r1[index - 1].fY); path1.lineTo(r1[index].fX, r1[index].fY); } path1.close(); REPORTER_ASSERT(reporter, fail ^ path1.isRect(0)); // fail, quad path1.reset(); path1.moveTo(r1[0].fX, r1[0].fY); for (index = 1; index < testLen[0] / sizeof(SkPoint); ++index) { if (index == 2) { path1.quadTo(1, .5f, 1, .5f); } path1.lineTo(r1[index].fX, r1[index].fY); } path1.close(); REPORTER_ASSERT(reporter, fail ^ path1.isRect(0)); // fail, cubic path1.reset(); path1.moveTo(r1[0].fX, r1[0].fY); for (index = 1; index < testLen[0] / sizeof(SkPoint); ++index) { if (index == 2) { path1.cubicTo(1, .5f, 1, .5f, 1, .5f); } path1.lineTo(r1[index].fX, r1[index].fY); } path1.close(); REPORTER_ASSERT(reporter, fail ^ path1.isRect(0)); }