/* * Copyright 2011 Google Inc. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "include/core/SkCanvas.h" #include "include/core/SkClipOp.h" #include "include/core/SkImageInfo.h" #include "include/core/SkMatrix.h" #include "include/core/SkPath.h" #include "include/core/SkPoint.h" #include "include/core/SkRRect.h" #include "include/core/SkRect.h" #include "include/core/SkRefCnt.h" #include "include/core/SkRegion.h" #include "include/core/SkScalar.h" #include "include/core/SkSize.h" #include "include/core/SkString.h" #include "include/core/SkSurface.h" #include "include/core/SkTypes.h" #include "include/effects/SkGradientShader.h" #include "include/private/SkTemplates.h" #include "include/utils/SkRandom.h" #include "src/core/SkClipStack.h" #include "tests/Test.h" #include #include #include static void test_assign_and_comparison(skiatest::Reporter* reporter) { SkClipStack s; bool doAA = false; REPORTER_ASSERT(reporter, 0 == s.getSaveCount()); // Build up a clip stack with a path, an empty clip, and a rect. s.save(); REPORTER_ASSERT(reporter, 1 == s.getSaveCount()); SkPath p; p.moveTo(5, 6); p.lineTo(7, 8); p.lineTo(5, 9); p.close(); s.clipPath(p, SkMatrix::I(), SkClipOp::kIntersect, doAA); s.save(); REPORTER_ASSERT(reporter, 2 == s.getSaveCount()); SkRect r = SkRect::MakeLTRB(1, 2, 103, 104); s.clipRect(r, SkMatrix::I(), SkClipOp::kIntersect, doAA); r = SkRect::MakeLTRB(4, 5, 56, 57); s.clipRect(r, SkMatrix::I(), SkClipOp::kIntersect, doAA); s.save(); REPORTER_ASSERT(reporter, 3 == s.getSaveCount()); r = SkRect::MakeLTRB(14, 15, 16, 17); s.clipRect(r, SkMatrix::I(), SkClipOp::kDifference, doAA); // Test that assignment works. SkClipStack copy = s; REPORTER_ASSERT(reporter, s == copy); // Test that different save levels triggers not equal. s.restore(); REPORTER_ASSERT(reporter, 2 == s.getSaveCount()); REPORTER_ASSERT(reporter, s != copy); // Test that an equal, but not copied version is equal. s.save(); REPORTER_ASSERT(reporter, 3 == s.getSaveCount()); r = SkRect::MakeLTRB(14, 15, 16, 17); s.clipRect(r, SkMatrix::I(), SkClipOp::kDifference, doAA); REPORTER_ASSERT(reporter, s == copy); // Test that a different op on one level triggers not equal. s.restore(); REPORTER_ASSERT(reporter, 2 == s.getSaveCount()); s.save(); REPORTER_ASSERT(reporter, 3 == s.getSaveCount()); r = SkRect::MakeLTRB(14, 15, 16, 17); s.clipRect(r, SkMatrix::I(), SkClipOp::kIntersect, doAA); REPORTER_ASSERT(reporter, s != copy); // Test that version constructed with rect-path rather than a rect is still considered equal. s.restore(); s.save(); SkPath rp; rp.addRect(r); s.clipPath(rp, SkMatrix::I(), SkClipOp::kDifference, doAA); REPORTER_ASSERT(reporter, s == copy); // Test that different rects triggers not equal. s.restore(); REPORTER_ASSERT(reporter, 2 == s.getSaveCount()); s.save(); REPORTER_ASSERT(reporter, 3 == s.getSaveCount()); r = SkRect::MakeLTRB(24, 25, 26, 27); s.clipRect(r, SkMatrix::I(), SkClipOp::kDifference, doAA); REPORTER_ASSERT(reporter, s != copy); s.restore(); REPORTER_ASSERT(reporter, 2 == s.getSaveCount()); copy.restore(); REPORTER_ASSERT(reporter, 2 == copy.getSaveCount()); REPORTER_ASSERT(reporter, s == copy); s.restore(); REPORTER_ASSERT(reporter, 1 == s.getSaveCount()); copy.restore(); REPORTER_ASSERT(reporter, 1 == copy.getSaveCount()); REPORTER_ASSERT(reporter, s == copy); // Test that different paths triggers not equal. s.restore(); REPORTER_ASSERT(reporter, 0 == s.getSaveCount()); s.save(); REPORTER_ASSERT(reporter, 1 == s.getSaveCount()); p.addRect(r); s.clipPath(p, SkMatrix::I(), SkClipOp::kIntersect, doAA); REPORTER_ASSERT(reporter, s != copy); } static void assert_count(skiatest::Reporter* reporter, const SkClipStack& stack, int count) { SkClipStack::B2TIter iter(stack); int counter = 0; while (iter.next()) { counter += 1; } REPORTER_ASSERT(reporter, count == counter); } // Exercise the SkClipStack's bottom to top and bidirectional iterators // (including the skipToTopmost functionality) static void test_iterators(skiatest::Reporter* reporter) { SkClipStack stack; static const SkRect gRects[] = { { 0, 0, 40, 40 }, { 60, 0, 100, 40 }, { 0, 60, 40, 100 }, { 60, 60, 100, 100 } }; for (size_t i = 0; i < SK_ARRAY_COUNT(gRects); i++) { // the difference op will prevent these from being fused together stack.clipRect(gRects[i], SkMatrix::I(), SkClipOp::kDifference, false); } assert_count(reporter, stack, 4); // bottom to top iteration { const SkClipStack::Element* element = nullptr; SkClipStack::B2TIter iter(stack); int i; for (i = 0, element = iter.next(); element; ++i, element = iter.next()) { REPORTER_ASSERT(reporter, SkClipStack::Element::DeviceSpaceType::kRect == element->getDeviceSpaceType()); REPORTER_ASSERT(reporter, element->getDeviceSpaceRect() == gRects[i]); } SkASSERT(i == 4); } // top to bottom iteration { const SkClipStack::Element* element = nullptr; SkClipStack::Iter iter(stack, SkClipStack::Iter::kTop_IterStart); int i; for (i = 3, element = iter.prev(); element; --i, element = iter.prev()) { REPORTER_ASSERT(reporter, SkClipStack::Element::DeviceSpaceType::kRect == element->getDeviceSpaceType()); REPORTER_ASSERT(reporter, element->getDeviceSpaceRect() == gRects[i]); } SkASSERT(i == -1); } // skipToTopmost { const SkClipStack::Element* element = nullptr; SkClipStack::Iter iter(stack, SkClipStack::Iter::kBottom_IterStart); element = iter.skipToTopmost(SkClipOp::kDifference); REPORTER_ASSERT(reporter, SkClipStack::Element::DeviceSpaceType::kRect == element->getDeviceSpaceType()); REPORTER_ASSERT(reporter, element->getDeviceSpaceRect() == gRects[3]); } } // Exercise the SkClipStack's getConservativeBounds computation static void test_bounds(skiatest::Reporter* reporter, SkClipStack::Element::DeviceSpaceType primType) { static const int gNumCases = 8; static const SkRect gAnswerRectsBW[gNumCases] = { // A op B { 40, 40, 50, 50 }, { 10, 10, 50, 50 }, // invA op B { 40, 40, 80, 80 }, { 0, 0, 100, 100 }, // A op invB { 10, 10, 50, 50 }, { 40, 40, 50, 50 }, // invA op invB { 0, 0, 100, 100 }, { 40, 40, 80, 80 }, }; static const SkClipOp gOps[] = { SkClipOp::kIntersect, SkClipOp::kDifference }; SkRect rectA, rectB; rectA.setLTRB(10, 10, 50, 50); rectB.setLTRB(40, 40, 80, 80); SkRRect rrectA, rrectB; rrectA.setOval(rectA); rrectB.setRectXY(rectB, SkIntToScalar(1), SkIntToScalar(2)); SkPath pathA, pathB; pathA.addRoundRect(rectA, SkIntToScalar(5), SkIntToScalar(5)); pathB.addRoundRect(rectB, SkIntToScalar(5), SkIntToScalar(5)); SkClipStack stack; SkRect devClipBound; bool isIntersectionOfRects = false; int testCase = 0; int numBitTests = SkClipStack::Element::DeviceSpaceType::kPath == primType ? 4 : 1; for (int invBits = 0; invBits < numBitTests; ++invBits) { for (size_t op = 0; op < SK_ARRAY_COUNT(gOps); ++op) { stack.save(); bool doInvA = SkToBool(invBits & 1); bool doInvB = SkToBool(invBits & 2); pathA.setFillType(doInvA ? SkPathFillType::kInverseEvenOdd : SkPathFillType::kEvenOdd); pathB.setFillType(doInvB ? SkPathFillType::kInverseEvenOdd : SkPathFillType::kEvenOdd); switch (primType) { case SkClipStack::Element::DeviceSpaceType::kShader: case SkClipStack::Element::DeviceSpaceType::kEmpty: SkDEBUGFAIL("Don't call this with kEmpty or kShader."); break; case SkClipStack::Element::DeviceSpaceType::kRect: stack.clipRect(rectA, SkMatrix::I(), SkClipOp::kIntersect, false); stack.clipRect(rectB, SkMatrix::I(), gOps[op], false); break; case SkClipStack::Element::DeviceSpaceType::kRRect: stack.clipRRect(rrectA, SkMatrix::I(), SkClipOp::kIntersect, false); stack.clipRRect(rrectB, SkMatrix::I(), gOps[op], false); break; case SkClipStack::Element::DeviceSpaceType::kPath: stack.clipPath(pathA, SkMatrix::I(), SkClipOp::kIntersect, false); stack.clipPath(pathB, SkMatrix::I(), gOps[op], false); break; } REPORTER_ASSERT(reporter, !stack.isWideOpen()); REPORTER_ASSERT(reporter, SkClipStack::kWideOpenGenID != stack.getTopmostGenID()); stack.getConservativeBounds(0, 0, 100, 100, &devClipBound, &isIntersectionOfRects); if (SkClipStack::Element::DeviceSpaceType::kRect == primType) { REPORTER_ASSERT(reporter, isIntersectionOfRects == (gOps[op] == SkClipOp::kIntersect)); } else { REPORTER_ASSERT(reporter, !isIntersectionOfRects); } SkASSERT(testCase < gNumCases); REPORTER_ASSERT(reporter, devClipBound == gAnswerRectsBW[testCase]); ++testCase; stack.restore(); } } } // Test out 'isWideOpen' entry point static void test_isWideOpen(skiatest::Reporter* reporter) { { // Empty stack is wide open. Wide open stack means that gen id is wide open. SkClipStack stack; REPORTER_ASSERT(reporter, stack.isWideOpen()); REPORTER_ASSERT(reporter, SkClipStack::kWideOpenGenID == stack.getTopmostGenID()); } SkRect rectA, rectB; rectA.setLTRB(10, 10, 40, 40); rectB.setLTRB(50, 50, 80, 80); // Stack should initially be wide open { SkClipStack stack; REPORTER_ASSERT(reporter, stack.isWideOpen()); REPORTER_ASSERT(reporter, SkClipStack::kWideOpenGenID == stack.getTopmostGenID()); } // Test out empty difference from a wide open clip { SkClipStack stack; SkRect emptyRect; emptyRect.setEmpty(); stack.clipRect(emptyRect, SkMatrix::I(), SkClipOp::kDifference, false); REPORTER_ASSERT(reporter, stack.isWideOpen()); REPORTER_ASSERT(reporter, SkClipStack::kWideOpenGenID == stack.getTopmostGenID()); } // Test out return to wide open { SkClipStack stack; stack.save(); stack.clipRect(rectA, SkMatrix::I(), SkClipOp::kIntersect, false); REPORTER_ASSERT(reporter, !stack.isWideOpen()); REPORTER_ASSERT(reporter, SkClipStack::kWideOpenGenID != stack.getTopmostGenID()); stack.restore(); REPORTER_ASSERT(reporter, stack.isWideOpen()); REPORTER_ASSERT(reporter, SkClipStack::kWideOpenGenID == stack.getTopmostGenID()); } } static int count(const SkClipStack& stack) { SkClipStack::Iter iter(stack, SkClipStack::Iter::kTop_IterStart); const SkClipStack::Element* element = nullptr; int count = 0; for (element = iter.prev(); element; element = iter.prev(), ++count) { } return count; } static void test_rect_inverse_fill(skiatest::Reporter* reporter) { // non-intersecting rectangles SkRect rect = SkRect::MakeLTRB(0, 0, 10, 10); SkPath path; path.addRect(rect); path.toggleInverseFillType(); SkClipStack stack; stack.clipPath(path, SkMatrix::I(), SkClipOp::kIntersect, false); SkRect bounds; SkClipStack::BoundsType boundsType; stack.getBounds(&bounds, &boundsType); REPORTER_ASSERT(reporter, SkClipStack::kInsideOut_BoundsType == boundsType); REPORTER_ASSERT(reporter, bounds == rect); } static void test_rect_replace(skiatest::Reporter* reporter) { SkRect rect = SkRect::MakeWH(100, 100); SkRect rect2 = SkRect::MakeXYWH(50, 50, 100, 100); SkRect bound; SkClipStack::BoundsType type; bool isIntersectionOfRects; // Adding a new rect with the replace operator should not increase // the stack depth. BW replacing BW. { SkClipStack stack; REPORTER_ASSERT(reporter, 0 == count(stack)); stack.replaceClip(rect, false); REPORTER_ASSERT(reporter, 1 == count(stack)); stack.replaceClip(rect, false); REPORTER_ASSERT(reporter, 1 == count(stack)); } // Adding a new rect with the replace operator should not increase // the stack depth. AA replacing AA. { SkClipStack stack; REPORTER_ASSERT(reporter, 0 == count(stack)); stack.replaceClip(rect, true); REPORTER_ASSERT(reporter, 1 == count(stack)); stack.replaceClip(rect, true); REPORTER_ASSERT(reporter, 1 == count(stack)); } // Adding a new rect with the replace operator should not increase // the stack depth. BW replacing AA replacing BW. { SkClipStack stack; REPORTER_ASSERT(reporter, 0 == count(stack)); stack.replaceClip(rect, false); REPORTER_ASSERT(reporter, 1 == count(stack)); stack.replaceClip(rect, true); REPORTER_ASSERT(reporter, 1 == count(stack)); stack.replaceClip(rect, false); REPORTER_ASSERT(reporter, 1 == count(stack)); } // Make sure replace clip rects don't collapse too much. { SkClipStack stack; stack.replaceClip(rect, false); stack.clipRect(rect2, SkMatrix::I(), SkClipOp::kIntersect, false); REPORTER_ASSERT(reporter, 1 == count(stack)); stack.save(); stack.replaceClip(rect, false); REPORTER_ASSERT(reporter, 2 == count(stack)); stack.getBounds(&bound, &type, &isIntersectionOfRects); REPORTER_ASSERT(reporter, bound == rect); stack.restore(); REPORTER_ASSERT(reporter, 1 == count(stack)); stack.save(); stack.replaceClip(rect, false); stack.replaceClip(rect, false); REPORTER_ASSERT(reporter, 2 == count(stack)); stack.restore(); REPORTER_ASSERT(reporter, 1 == count(stack)); stack.save(); stack.replaceClip(rect, false); stack.clipRect(rect2, SkMatrix::I(), SkClipOp::kIntersect, false); stack.replaceClip(rect, false); REPORTER_ASSERT(reporter, 2 == count(stack)); stack.restore(); REPORTER_ASSERT(reporter, 1 == count(stack)); } } // Simplified path-based version of test_rect_replace. static void test_path_replace(skiatest::Reporter* reporter) { auto replacePath = [](SkClipStack* stack, const SkPath& path, bool doAA) { const SkRect wideOpen = SkRect::MakeLTRB(-1000, -1000, 1000, 1000); stack->replaceClip(wideOpen, false); stack->clipPath(path, SkMatrix::I(), SkClipOp::kIntersect, doAA); }; SkRect rect = SkRect::MakeWH(100, 100); SkPath path; path.addCircle(50, 50, 50); // Emulating replace operations with more complex geometry is not atomic, it's a replace // with a wide-open rect and then an intersection with the complex geometry. The replace can // combine with prior elements, but the subsequent intersect cannot be combined so the stack // continues to grow. { SkClipStack stack; REPORTER_ASSERT(reporter, 0 == count(stack)); replacePath(&stack, path, false); REPORTER_ASSERT(reporter, 2 == count(stack)); replacePath(&stack, path, false); REPORTER_ASSERT(reporter, 2 == count(stack)); } // Replacing rect with path. { SkClipStack stack; stack.replaceClip(rect, true); REPORTER_ASSERT(reporter, 1 == count(stack)); replacePath(&stack, path, true); REPORTER_ASSERT(reporter, 2 == count(stack)); } } // Test out SkClipStack's merging of rect clips. In particular exercise // merging of aa vs. bw rects. static void test_rect_merging(skiatest::Reporter* reporter) { SkRect overlapLeft = SkRect::MakeLTRB(10, 10, 50, 50); SkRect overlapRight = SkRect::MakeLTRB(40, 40, 80, 80); SkRect nestedParent = SkRect::MakeLTRB(10, 10, 90, 90); SkRect nestedChild = SkRect::MakeLTRB(40, 40, 60, 60); SkRect bound; SkClipStack::BoundsType type; bool isIntersectionOfRects; // all bw overlapping - should merge { SkClipStack stack; stack.clipRect(overlapLeft, SkMatrix::I(), SkClipOp::kIntersect, false); stack.clipRect(overlapRight, SkMatrix::I(), SkClipOp::kIntersect, false); REPORTER_ASSERT(reporter, 1 == count(stack)); stack.getBounds(&bound, &type, &isIntersectionOfRects); REPORTER_ASSERT(reporter, isIntersectionOfRects); } // all aa overlapping - should merge { SkClipStack stack; stack.clipRect(overlapLeft, SkMatrix::I(), SkClipOp::kIntersect, true); stack.clipRect(overlapRight, SkMatrix::I(), SkClipOp::kIntersect, true); REPORTER_ASSERT(reporter, 1 == count(stack)); stack.getBounds(&bound, &type, &isIntersectionOfRects); REPORTER_ASSERT(reporter, isIntersectionOfRects); } // mixed overlapping - should _not_ merge { SkClipStack stack; stack.clipRect(overlapLeft, SkMatrix::I(), SkClipOp::kIntersect, true); stack.clipRect(overlapRight, SkMatrix::I(), SkClipOp::kIntersect, false); REPORTER_ASSERT(reporter, 2 == count(stack)); stack.getBounds(&bound, &type, &isIntersectionOfRects); REPORTER_ASSERT(reporter, !isIntersectionOfRects); } // mixed nested (bw inside aa) - should merge { SkClipStack stack; stack.clipRect(nestedParent, SkMatrix::I(), SkClipOp::kIntersect, true); stack.clipRect(nestedChild, SkMatrix::I(), SkClipOp::kIntersect, false); REPORTER_ASSERT(reporter, 1 == count(stack)); stack.getBounds(&bound, &type, &isIntersectionOfRects); REPORTER_ASSERT(reporter, isIntersectionOfRects); } // mixed nested (aa inside bw) - should merge { SkClipStack stack; stack.clipRect(nestedParent, SkMatrix::I(), SkClipOp::kIntersect, false); stack.clipRect(nestedChild, SkMatrix::I(), SkClipOp::kIntersect, true); REPORTER_ASSERT(reporter, 1 == count(stack)); stack.getBounds(&bound, &type, &isIntersectionOfRects); REPORTER_ASSERT(reporter, isIntersectionOfRects); } // reverse nested (aa inside bw) - should _not_ merge { SkClipStack stack; stack.clipRect(nestedChild, SkMatrix::I(), SkClipOp::kIntersect, false); stack.clipRect(nestedParent, SkMatrix::I(), SkClipOp::kIntersect, true); REPORTER_ASSERT(reporter, 2 == count(stack)); stack.getBounds(&bound, &type, &isIntersectionOfRects); REPORTER_ASSERT(reporter, !isIntersectionOfRects); } } static void test_quickContains(skiatest::Reporter* reporter) { SkRect testRect = SkRect::MakeLTRB(10, 10, 40, 40); SkRect insideRect = SkRect::MakeLTRB(20, 20, 30, 30); SkRect intersectingRect = SkRect::MakeLTRB(25, 25, 50, 50); SkRect outsideRect = SkRect::MakeLTRB(0, 0, 50, 50); SkRect nonIntersectingRect = SkRect::MakeLTRB(100, 100, 110, 110); SkPath insideCircle; insideCircle.addCircle(25, 25, 5); SkPath intersectingCircle; intersectingCircle.addCircle(25, 40, 10); SkPath outsideCircle; outsideCircle.addCircle(25, 25, 50); SkPath nonIntersectingCircle; nonIntersectingCircle.addCircle(100, 100, 5); { SkClipStack stack; stack.clipRect(outsideRect, SkMatrix::I(), SkClipOp::kDifference, false); // return false because quickContains currently does not care for kDifference REPORTER_ASSERT(reporter, false == stack.quickContains(testRect)); } // Replace Op tests { SkClipStack stack; stack.replaceClip(outsideRect, false); REPORTER_ASSERT(reporter, true == stack.quickContains(testRect)); } { SkClipStack stack; stack.clipRect(insideRect, SkMatrix::I(), SkClipOp::kIntersect, false); stack.save(); // To prevent in-place substitution by replace OP stack.replaceClip(outsideRect, false); REPORTER_ASSERT(reporter, true == stack.quickContains(testRect)); stack.restore(); } { SkClipStack stack; stack.clipRect(outsideRect, SkMatrix::I(), SkClipOp::kIntersect, false); stack.save(); // To prevent in-place substitution by replace OP stack.replaceClip(insideRect, false); REPORTER_ASSERT(reporter, false == stack.quickContains(testRect)); stack.restore(); } // Verify proper traversal of multi-element clip { SkClipStack stack; stack.clipRect(insideRect, SkMatrix::I(), SkClipOp::kIntersect, false); // Use a path for second clip to prevent in-place intersection stack.clipPath(outsideCircle, SkMatrix::I(), SkClipOp::kIntersect, false); REPORTER_ASSERT(reporter, false == stack.quickContains(testRect)); } // Intersect Op tests with rectangles { SkClipStack stack; stack.clipRect(outsideRect, SkMatrix::I(), SkClipOp::kIntersect, false); REPORTER_ASSERT(reporter, true == stack.quickContains(testRect)); } { SkClipStack stack; stack.clipRect(insideRect, SkMatrix::I(), SkClipOp::kIntersect, false); REPORTER_ASSERT(reporter, false == stack.quickContains(testRect)); } { SkClipStack stack; stack.clipRect(intersectingRect, SkMatrix::I(), SkClipOp::kIntersect, false); REPORTER_ASSERT(reporter, false == stack.quickContains(testRect)); } { SkClipStack stack; stack.clipRect(nonIntersectingRect, SkMatrix::I(), SkClipOp::kIntersect, false); REPORTER_ASSERT(reporter, false == stack.quickContains(testRect)); } // Intersect Op tests with circle paths { SkClipStack stack; stack.clipPath(outsideCircle, SkMatrix::I(), SkClipOp::kIntersect, false); REPORTER_ASSERT(reporter, true == stack.quickContains(testRect)); } { SkClipStack stack; stack.clipPath(insideCircle, SkMatrix::I(), SkClipOp::kIntersect, false); REPORTER_ASSERT(reporter, false == stack.quickContains(testRect)); } { SkClipStack stack; stack.clipPath(intersectingCircle, SkMatrix::I(), SkClipOp::kIntersect, false); REPORTER_ASSERT(reporter, false == stack.quickContains(testRect)); } { SkClipStack stack; stack.clipPath(nonIntersectingCircle, SkMatrix::I(), SkClipOp::kIntersect, false); REPORTER_ASSERT(reporter, false == stack.quickContains(testRect)); } // Intersect Op tests with inverse filled rectangles { SkClipStack stack; SkPath path; path.addRect(outsideRect); path.toggleInverseFillType(); stack.clipPath(path, SkMatrix::I(), SkClipOp::kIntersect, false); REPORTER_ASSERT(reporter, false == stack.quickContains(testRect)); } { SkClipStack stack; SkPath path; path.addRect(insideRect); path.toggleInverseFillType(); stack.clipPath(path, SkMatrix::I(), SkClipOp::kIntersect, false); REPORTER_ASSERT(reporter, false == stack.quickContains(testRect)); } { SkClipStack stack; SkPath path; path.addRect(intersectingRect); path.toggleInverseFillType(); stack.clipPath(path, SkMatrix::I(), SkClipOp::kIntersect, false); REPORTER_ASSERT(reporter, false == stack.quickContains(testRect)); } { SkClipStack stack; SkPath path; path.addRect(nonIntersectingRect); path.toggleInverseFillType(); stack.clipPath(path, SkMatrix::I(), SkClipOp::kIntersect, false); REPORTER_ASSERT(reporter, true == stack.quickContains(testRect)); } // Intersect Op tests with inverse filled circles { SkClipStack stack; SkPath path = outsideCircle; path.toggleInverseFillType(); stack.clipPath(path, SkMatrix::I(), SkClipOp::kIntersect, false); REPORTER_ASSERT(reporter, false == stack.quickContains(testRect)); } { SkClipStack stack; SkPath path = insideCircle; path.toggleInverseFillType(); stack.clipPath(path, SkMatrix::I(), SkClipOp::kIntersect, false); REPORTER_ASSERT(reporter, false == stack.quickContains(testRect)); } { SkClipStack stack; SkPath path = intersectingCircle; path.toggleInverseFillType(); stack.clipPath(path, SkMatrix::I(), SkClipOp::kIntersect, false); REPORTER_ASSERT(reporter, false == stack.quickContains(testRect)); } { SkClipStack stack; SkPath path = nonIntersectingCircle; path.toggleInverseFillType(); stack.clipPath(path, SkMatrix::I(), SkClipOp::kIntersect, false); REPORTER_ASSERT(reporter, true == stack.quickContains(testRect)); } } static void set_region_to_stack(const SkClipStack& stack, const SkIRect& bounds, SkRegion* region) { region->setRect(bounds); SkClipStack::Iter iter(stack, SkClipStack::Iter::kBottom_IterStart); while (const SkClipStack::Element *element = iter.next()) { SkRegion elemRegion; SkRegion boundsRgn(bounds); SkPath path; switch (element->getDeviceSpaceType()) { case SkClipStack::Element::DeviceSpaceType::kEmpty: elemRegion.setEmpty(); break; default: element->asDeviceSpacePath(&path); elemRegion.setPath(path, boundsRgn); break; } region->op(elemRegion, element->isReplaceOp() ? SkRegion::kReplace_Op : (SkRegion::Op) element->getOp()); } } static void test_invfill_diff_bug(skiatest::Reporter* reporter) { SkClipStack stack; stack.clipRect({10, 10, 20, 20}, SkMatrix::I(), SkClipOp::kIntersect, false); SkPath path; path.addRect({30, 10, 40, 20}); path.setFillType(SkPathFillType::kInverseWinding); stack.clipPath(path, SkMatrix::I(), SkClipOp::kDifference, false); REPORTER_ASSERT(reporter, SkClipStack::kEmptyGenID == stack.getTopmostGenID()); SkRect stackBounds; SkClipStack::BoundsType stackBoundsType; stack.getBounds(&stackBounds, &stackBoundsType); REPORTER_ASSERT(reporter, stackBounds.isEmpty()); REPORTER_ASSERT(reporter, SkClipStack::kNormal_BoundsType == stackBoundsType); SkRegion region; set_region_to_stack(stack, {0, 0, 50, 30}, ®ion); REPORTER_ASSERT(reporter, region.isEmpty()); } /////////////////////////////////////////////////////////////////////////////////////////////////// static void test_is_rrect_deep_rect_stack(skiatest::Reporter* reporter) { static constexpr SkRect kTargetBounds = SkRect::MakeWH(1000, 500); // All antialiased or all not antialiased. for (bool aa : {false, true}) { SkClipStack stack; for (int i = 0; i <= 100; ++i) { stack.save(); stack.clipRect(SkRect::MakeLTRB(i, 0.5, kTargetBounds.width(), kTargetBounds.height()), SkMatrix::I(), SkClipOp::kIntersect, aa); } SkRRect rrect; bool isAA; SkRRect expected = SkRRect::MakeRect( SkRect::MakeLTRB(100, 0.5, kTargetBounds.width(), kTargetBounds.height())); if (stack.isRRect(kTargetBounds, &rrect, &isAA)) { REPORTER_ASSERT(reporter, rrect == expected); REPORTER_ASSERT(reporter, aa == isAA); } else { ERRORF(reporter, "Expected to be an rrect."); } } // Mixed AA and non-AA without simple containment. SkClipStack stack; for (int i = 0; i <= 100; ++i) { bool aa = i & 0b1; int j = 100 - i; stack.save(); stack.clipRect(SkRect::MakeLTRB(i, j + 0.5, kTargetBounds.width(), kTargetBounds.height()), SkMatrix::I(), SkClipOp::kIntersect, aa); } SkRRect rrect; bool isAA; REPORTER_ASSERT(reporter, !stack.isRRect(kTargetBounds, &rrect, &isAA)); } DEF_TEST(ClipStack, reporter) { SkClipStack stack; REPORTER_ASSERT(reporter, 0 == stack.getSaveCount()); assert_count(reporter, stack, 0); static const SkIRect gRects[] = { { 0, 0, 100, 100 }, { 25, 25, 125, 125 }, { 0, 0, 1000, 1000 }, { 0, 0, 75, 75 } }; for (size_t i = 0; i < SK_ARRAY_COUNT(gRects); i++) { stack.clipDevRect(gRects[i], SkClipOp::kIntersect); } // all of the above rects should have been intersected, leaving only 1 rect SkClipStack::B2TIter iter(stack); const SkClipStack::Element* element = iter.next(); SkRect answer; answer.setLTRB(25, 25, 75, 75); REPORTER_ASSERT(reporter, element); REPORTER_ASSERT(reporter, SkClipStack::Element::DeviceSpaceType::kRect == element->getDeviceSpaceType()); REPORTER_ASSERT(reporter, SkClipOp::kIntersect == element->getOp()); REPORTER_ASSERT(reporter, element->getDeviceSpaceRect() == answer); // now check that we only had one in our iterator REPORTER_ASSERT(reporter, !iter.next()); stack.reset(); REPORTER_ASSERT(reporter, 0 == stack.getSaveCount()); assert_count(reporter, stack, 0); test_assign_and_comparison(reporter); test_iterators(reporter); test_bounds(reporter, SkClipStack::Element::DeviceSpaceType::kRect); test_bounds(reporter, SkClipStack::Element::DeviceSpaceType::kRRect); test_bounds(reporter, SkClipStack::Element::DeviceSpaceType::kPath); test_isWideOpen(reporter); test_rect_merging(reporter); test_rect_replace(reporter); test_rect_inverse_fill(reporter); test_path_replace(reporter); test_quickContains(reporter); test_invfill_diff_bug(reporter); test_is_rrect_deep_rect_stack(reporter); }