blob: bff07a704e281cc850c80f606f431156931ede82 [file]
/*
* 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/SkMatrix.h"
#include "include/core/SkPath.h"
#include "include/core/SkPathBuilder.h"
#include "include/core/SkPathTypes.h"
#include "include/core/SkPoint.h"
#include "include/core/SkRRect.h"
#include "include/core/SkRect.h"
#include "include/core/SkRegion.h"
#include "include/core/SkScalar.h"
#include "include/core/SkTypes.h"
#include "include/private/base/SkDebug.h"
#include "src/base/SkAutoMalloc.h"
#include "src/base/SkRandom.h"
#include "src/core/SkScan.h"
#include "tests/Test.h"
#include <array>
#include <cstddef>
#include <cstdint>
static void Union(SkRegion* rgn, const SkIRect& rect) {
rgn->op(rect, SkRegion::kUnion_Op);
}
#define TEST_NO_INTERSECT(rgn, rect) REPORTER_ASSERT(reporter, !rgn.intersects(rect))
#define TEST_INTERSECT(rgn, rect) REPORTER_ASSERT(reporter, rgn.intersects(rect))
#define TEST_NO_CONTAINS(rgn, rect) REPORTER_ASSERT(reporter, !rgn.contains(rect))
// inspired by https://issues.skia.org/issues/40032017
//
static void test_fromchrome(skiatest::Reporter* reporter) {
SkRegion r;
Union(&r, SkIRect::MakeXYWH(0, 0, 1, 1));
TEST_NO_INTERSECT(r, SkIRect::MakeXYWH(0, 0, 0, 0));
TEST_INTERSECT(r, SkIRect::MakeXYWH(0, 0, 2, 2));
TEST_INTERSECT(r, SkIRect::MakeXYWH(-1, 0, 2, 2));
TEST_INTERSECT(r, SkIRect::MakeXYWH(-1, -1, 2, 2));
TEST_INTERSECT(r, SkIRect::MakeXYWH(0, -1, 2, 2));
TEST_INTERSECT(r, SkIRect::MakeXYWH(-1, -1, 3, 3));
Union(&r, SkIRect::MakeXYWH(0, 0, 3, 3));
Union(&r, SkIRect::MakeXYWH(10, 0, 3, 3));
Union(&r, SkIRect::MakeXYWH(0, 10, 13, 3));
TEST_INTERSECT(r, SkIRect::MakeXYWH(-1, -1, 2, 2));
TEST_INTERSECT(r, SkIRect::MakeXYWH(2, -1, 2, 2));
TEST_INTERSECT(r, SkIRect::MakeXYWH(2, 2, 2, 2));
TEST_INTERSECT(r, SkIRect::MakeXYWH(-1, 2, 2, 2));
TEST_INTERSECT(r, SkIRect::MakeXYWH(9, -1, 2, 2));
TEST_INTERSECT(r, SkIRect::MakeXYWH(12, -1, 2, 2));
TEST_INTERSECT(r, SkIRect::MakeXYWH(12, 2, 2, 2));
TEST_INTERSECT(r, SkIRect::MakeXYWH(9, 2, 2, 2));
TEST_INTERSECT(r, SkIRect::MakeXYWH(0, -1, 13, 5));
TEST_INTERSECT(r, SkIRect::MakeXYWH(1, -1, 11, 5));
TEST_INTERSECT(r, SkIRect::MakeXYWH(2, -1, 9, 5));
TEST_INTERSECT(r, SkIRect::MakeXYWH(2, -1, 8, 5));
TEST_INTERSECT(r, SkIRect::MakeXYWH(3, -1, 8, 5));
TEST_INTERSECT(r, SkIRect::MakeXYWH(0, 1, 13, 1));
TEST_INTERSECT(r, SkIRect::MakeXYWH(1, 1, 11, 1));
TEST_INTERSECT(r, SkIRect::MakeXYWH(2, 1, 9, 1));
TEST_INTERSECT(r, SkIRect::MakeXYWH(2, 1, 8, 1));
TEST_INTERSECT(r, SkIRect::MakeXYWH(3, 1, 8, 1));
TEST_INTERSECT(r, SkIRect::MakeXYWH(0, 0, 13, 13));
TEST_INTERSECT(r, SkIRect::MakeXYWH(0, 1, 13, 11));
TEST_INTERSECT(r, SkIRect::MakeXYWH(0, 2, 13, 9));
TEST_INTERSECT(r, SkIRect::MakeXYWH(0, 2, 13, 8));
// These test SkRegion::contains(Rect) and SkRegion::contains(Region)
SkRegion container;
Union(&container, SkIRect::MakeXYWH(0, 0, 40, 20));
Union(&container, SkIRect::MakeXYWH(30, 20, 10, 20));
TEST_NO_CONTAINS(container, SkIRect::MakeXYWH(0, 0, 10, 39));
TEST_NO_CONTAINS(container, SkIRect::MakeXYWH(29, 0, 10, 39));
{
SkRegion rgn;
Union(&rgn, SkIRect::MakeXYWH(0, 0, 10, 10));
Union(&rgn, SkIRect::MakeLTRB(5, 10, 20, 20));
TEST_INTERSECT(rgn, SkIRect::MakeXYWH(15, 0, 5, 11));
}
}
static void test_empties(skiatest::Reporter* reporter) {
SkRegion valid(SkIRect::MakeWH(10, 10));
SkRegion empty, empty2;
REPORTER_ASSERT(reporter, empty.isEmpty());
REPORTER_ASSERT(reporter, !valid.isEmpty());
// test intersects
REPORTER_ASSERT(reporter, !empty.intersects(empty2));
REPORTER_ASSERT(reporter, !valid.intersects(empty));
// test contains
REPORTER_ASSERT(reporter, !empty.contains(empty2));
REPORTER_ASSERT(reporter, !valid.contains(empty));
REPORTER_ASSERT(reporter, !empty.contains(valid));
SkPath emptyPath = SkPathBuilder()
.moveTo(1, 5)
.close()
.detach();
SkRegion openClip;
openClip.setRect({-16000, -16000, 16000, 16000});
empty.setPath(emptyPath, openClip); // should not assert
}
enum {
W = 256,
H = 256
};
static SkIRect randRect(SkRandom& rand) {
int x = rand.nextU() % W;
int y = rand.nextU() % H;
int w = rand.nextU() % W;
int h = rand.nextU() % H;
return SkIRect::MakeXYWH(x, y, w >> 1, h >> 1);
}
static void randRgn(SkRandom& rand, SkRegion* rgn, int n) {
rgn->setEmpty();
for (int i = 0; i < n; ++i) {
rgn->op(randRect(rand), SkRegion::kUnion_Op);
}
}
static bool slow_contains(const SkRegion& outer, const SkRegion& inner) {
SkRegion tmp;
tmp.op(outer, inner, SkRegion::kUnion_Op);
return outer == tmp;
}
static bool slow_contains(const SkRegion& outer, const SkIRect& r) {
SkRegion tmp;
tmp.op(outer, SkRegion(r), SkRegion::kUnion_Op);
return outer == tmp;
}
static bool slow_intersects(const SkRegion& outer, const SkRegion& inner) {
SkRegion tmp;
return tmp.op(outer, inner, SkRegion::kIntersect_Op);
}
static void test_contains_iter(skiatest::Reporter* reporter, const SkRegion& rgn) {
SkRegion::Iterator iter(rgn);
while (!iter.done()) {
SkIRect r = iter.rect();
REPORTER_ASSERT(reporter, rgn.contains(r));
r.inset(-1, -1);
REPORTER_ASSERT(reporter, !rgn.contains(r));
iter.next();
}
}
static void contains_proc(skiatest::Reporter* reporter,
const SkRegion& a, const SkRegion& b) {
// test rgn
bool c0 = a.contains(b);
bool c1 = slow_contains(a, b);
REPORTER_ASSERT(reporter, c0 == c1);
// test rect
SkIRect r = a.getBounds();
r.inset(r.width()/4, r.height()/4);
c0 = a.contains(r);
c1 = slow_contains(a, r);
REPORTER_ASSERT(reporter, c0 == c1);
test_contains_iter(reporter, a);
test_contains_iter(reporter, b);
}
static void test_intersects_iter(skiatest::Reporter* reporter, const SkRegion& rgn) {
SkRegion::Iterator iter(rgn);
while (!iter.done()) {
SkIRect r = iter.rect();
REPORTER_ASSERT(reporter, rgn.intersects(r));
r.inset(-1, -1);
REPORTER_ASSERT(reporter, rgn.intersects(r));
iter.next();
}
}
static void intersects_proc(skiatest::Reporter* reporter,
const SkRegion& a, const SkRegion& b) {
bool c0 = a.intersects(b);
bool c1 = slow_intersects(a, b);
REPORTER_ASSERT(reporter, c0 == c1);
test_intersects_iter(reporter, a);
test_intersects_iter(reporter, b);
}
static void test_proc(skiatest::Reporter* reporter,
void (*proc)(skiatest::Reporter*,
const SkRegion& a, const SkRegion&)) {
SkRandom rand;
for (int i = 0; i < 10000; ++i) {
SkRegion outer;
randRgn(rand, &outer, 8);
SkRegion inner;
randRgn(rand, &inner, 2);
proc(reporter, outer, inner);
}
}
DEF_TEST(Region, reporter) {
test_proc(reporter, contains_proc);
test_proc(reporter, intersects_proc);
test_empties(reporter);
test_fromchrome(reporter);
}
// Test that writeToMemory reports the same number of bytes whether there was a
// buffer to write to or not.
static void test_write(const SkRegion& region, skiatest::Reporter* r) {
const size_t bytesNeeded = region.writeToMemory(nullptr);
SkAutoMalloc storage(bytesNeeded);
const size_t bytesWritten = region.writeToMemory(storage.get());
REPORTER_ASSERT(r, bytesWritten == bytesNeeded);
// Also check that the bytes are meaningful.
SkRegion copy;
REPORTER_ASSERT(r, copy.readFromMemory(storage.get(), bytesNeeded));
REPORTER_ASSERT(r, region == copy);
}
DEF_TEST(Region_writeToMemory, r) {
// Test an empty region.
SkRegion region;
REPORTER_ASSERT(r, region.isEmpty());
test_write(region, r);
// Test a rectangular region
bool nonEmpty = region.setRect({0, 0, 50, 50});
REPORTER_ASSERT(r, nonEmpty);
REPORTER_ASSERT(r, region.isRect());
test_write(region, r);
// Test a complex region
nonEmpty = region.op({50, 50, 100, 100}, SkRegion::kUnion_Op);
REPORTER_ASSERT(r, nonEmpty);
REPORTER_ASSERT(r, region.isComplex());
test_write(region, r);
SkRegion complexRegion;
Union(&complexRegion, SkIRect::MakeXYWH(0, 0, 1, 1));
Union(&complexRegion, SkIRect::MakeXYWH(0, 0, 3, 3));
Union(&complexRegion, SkIRect::MakeXYWH(10, 0, 3, 3));
Union(&complexRegion, SkIRect::MakeXYWH(0, 10, 13, 3));
test_write(complexRegion, r);
Union(&complexRegion, SkIRect::MakeXYWH(10, 20, 3, 3));
Union(&complexRegion, SkIRect::MakeXYWH(0, 20, 3, 3));
test_write(complexRegion, r);
}
DEF_TEST(Region_readFromMemory_bad, r) {
// These assume what our binary format is: conceivably we could change it
// and might need to remove or change some of these tests.
SkRegion region;
{
// invalid boundary rectangle
int32_t data[5] = {0, 4, 4, 8, 2};
REPORTER_ASSERT(r, 0 == region.readFromMemory(data, sizeof(data)));
}
// Region Layout, Serialized Format:
// COUNT LEFT TOP RIGHT BOTTOM Y_SPAN_COUNT TOTAL_INTERVAL_COUNT
// Top ( Bottom Span_Interval_Count ( Left Right )* Sentinel )+ Sentinel
{
// Example of valid data
int32_t data[] = {9, 0, 0, 10, 10, 1, 2, 0, 10, 2, 0, 4, 6, 10,
2147483647, 2147483647};
REPORTER_ASSERT(r, 0 != region.readFromMemory(data, sizeof(data)));
}
{
// Example of valid data with 4 intervals
int32_t data[] = {19, 0, 0, 30, 30, 3, 4, 0, 10, 2, 0, 10, 20, 30,
2147483647, 20, 0, 2147483647, 30, 2, 0, 10, 20, 30,
2147483647, 2147483647};
REPORTER_ASSERT(r, 0 != region.readFromMemory(data, sizeof(data)));
}
{
// Short count
int32_t data[] = {8, 0, 0, 10, 10, 1, 2, 0, 10, 2, 0, 4, 6, 10,
2147483647, 2147483647};
REPORTER_ASSERT(r, 0 == region.readFromMemory(data, sizeof(data)));
}
{
// bounds don't match
int32_t data[] = {9, 0, 0, 10, 11, 1, 2, 0, 10, 2, 0, 4, 6, 10,
2147483647, 2147483647};
REPORTER_ASSERT(r, 0 == region.readFromMemory(data, sizeof(data)));
}
{
// bad yspan count
int32_t data[] = {9, 0, 0, 10, 10, 2, 2, 0, 10, 2, 0, 4, 6, 10,
2147483647, 2147483647};
REPORTER_ASSERT(r, 0 == region.readFromMemory(data, sizeof(data)));
}
{
// bad int count
int32_t data[] = {9, 0, 0, 10, 10, 1, 3, 0, 10, 2, 0, 4, 6, 10,
2147483647, 2147483647};
REPORTER_ASSERT(r, 0 == region.readFromMemory(data, sizeof(data)));
}
{
// bad final sentinal
int32_t data[] = {9, 0, 0, 10, 10, 1, 2, 0, 10, 2, 0, 4, 6, 10,
2147483647, -1};
REPORTER_ASSERT(r, 0 == region.readFromMemory(data, sizeof(data)));
}
{
// bad row sentinal
int32_t data[] = {9, 0, 0, 10, 10, 1, 2, 0, 10, 2, 0, 4, 6, 10,
-1, 2147483647};
REPORTER_ASSERT(r, 0 == region.readFromMemory(data, sizeof(data)));
}
{
// starts with empty yspan
int32_t data[] = {12, 0, 0, 10, 10, 2, 2, -5, 0, 0, 2147483647, 10,
2, 0, 4, 6, 10, 2147483647, 2147483647};
REPORTER_ASSERT(r, 0 == region.readFromMemory(data, sizeof(data)));
}
{
// ends with empty yspan
int32_t data[] = {12, 0, 0, 10, 10, 2, 2, 0, 10, 2, 0, 4, 6, 10,
2147483647, 15, 0, 2147483647, 2147483647};
REPORTER_ASSERT(r, 0 == region.readFromMemory(data, sizeof(data)));
}
{
// y intervals out of order
int32_t data[] = {19, 0, -20, 30, 10, 3, 4, 0, 10, 2, 0, 10, 20, 30,
2147483647, -20, 0, 2147483647, -10, 2, 0, 10, 20, 30,
2147483647, 2147483647};
REPORTER_ASSERT(r, 0 == region.readFromMemory(data, sizeof(data)));
}
{
// x intervals out of order
int32_t data[] = {9, 0, 0, 10, 10, 1, 2, 0, 10, 2, 6, 10, 0, 4,
2147483647, 2147483647};
REPORTER_ASSERT(r, 0 == region.readFromMemory(data, sizeof(data)));
}
}
DEF_TEST(region_toobig, reporter) {
const int big = 1 << 30;
const SkIRect neg = SkIRect::MakeXYWH(-big, -big, 10, 10);
const SkIRect pos = SkIRect::MakeXYWH( big, big, 10, 10);
REPORTER_ASSERT(reporter, !neg.isEmpty());
REPORTER_ASSERT(reporter, !pos.isEmpty());
SkRegion negR(neg);
SkRegion posR(pos);
REPORTER_ASSERT(reporter, !negR.isEmpty());
REPORTER_ASSERT(reporter, !posR.isEmpty());
SkRegion rgn;
rgn.op(negR, posR, SkRegion::kUnion_Op);
// If we union those to rectangles, the resulting coordinates span more than int32_t, so
// we must mark the region as empty.
REPORTER_ASSERT(reporter, rgn.isEmpty());
}
DEF_TEST(region_inverse_union_skbug_7491, reporter) {
SkPath path = SkPathBuilder(SkPathFillType::kInverseWinding)
.moveTo(10, 20)
.lineTo(10, 30)
.lineTo(10.1f, 10)
.close()
.detach();
SkRegion clip;
clip.op(SkIRect::MakeLTRB(10, 10, 15, 20), SkRegion::kUnion_Op);
clip.op(SkIRect::MakeLTRB(20, 10, 25, 20), SkRegion::kUnion_Op);
SkRegion rgn;
rgn.setPath(path, clip);
REPORTER_ASSERT(reporter, clip == rgn);
}
DEF_TEST(giant_path_region, reporter) {
const SkScalar big = 32767;
SkPath path = SkPathBuilder()
.moveTo(-big, 0)
.quadTo(big, 0, big, big)
.detach();
SkIRect ir = path.getBounds().round();
SkRegion rgn;
rgn.setPath(path, SkRegion(ir));
}
DEF_TEST(rrect_region_crbug_850350, reporter) {
SkMatrix m;
m.reset();
m[1] = 0.753662348f;
m[3] = 1.40079998E+20f;
const SkPoint corners[] = {
{ 2.65876e-19f, 0.0194088f },
{ 4896, 0.00114702f },
{ 0, 0 },
{ 0.00114702f, 0.00495333f },
};
SkRRect rrect;
rrect.setRectRadii({-8.72387e-31f, 1.29996e-38f, 4896, 1.125f}, corners);
SkPath path = SkPath::RRect(rrect).makeTransform(m);
SkRegion rgn;
rgn.setPath(path, SkRegion{SkIRect{0, 0, 24, 24}});
}
DEF_TEST(region_bug_chromium_873051, reporter) {
SkRegion region;
REPORTER_ASSERT(reporter, region.setRect({0, 0, 0x7FFFFFFE, 0x7FFFFFFE}));
REPORTER_ASSERT(reporter, !region.setRect({0, 0, 0x7FFFFFFE, 0x7FFFFFFF}));
REPORTER_ASSERT(reporter, !region.setRect({0, 0, 0x7FFFFFFF, 0x7FFFFFFE}));
REPORTER_ASSERT(reporter, !region.setRect({0, 0, 0x7FFFFFFF, 0x7FFFFFFF}));
}
DEF_TEST(region_empty_iter, reporter) {
SkRegion::Iterator emptyIter;
REPORTER_ASSERT(reporter, !emptyIter.rewind());
REPORTER_ASSERT(reporter, emptyIter.done());
auto eRect = emptyIter.rect();
REPORTER_ASSERT(reporter, eRect.isEmpty());
REPORTER_ASSERT(reporter, SkIRect::MakeEmpty() == eRect);
REPORTER_ASSERT(reporter, !emptyIter.rgn());
SkRegion region;
SkRegion::Iterator resetIter;
resetIter.reset(region);
REPORTER_ASSERT(reporter, resetIter.rewind());
REPORTER_ASSERT(reporter, resetIter.done());
auto rRect = resetIter.rect();
REPORTER_ASSERT(reporter, rRect.isEmpty());
REPORTER_ASSERT(reporter, SkIRect::MakeEmpty() == rRect);
REPORTER_ASSERT(reporter, resetIter.rgn());
REPORTER_ASSERT(reporter, resetIter.rgn()->isEmpty());
SkRegion::Iterator iter(region);
REPORTER_ASSERT(reporter, iter.done());
auto iRect = iter.rect();
REPORTER_ASSERT(reporter, iRect.isEmpty());
REPORTER_ASSERT(reporter, SkIRect::MakeEmpty() == iRect);
REPORTER_ASSERT(reporter, iter.rgn());
REPORTER_ASSERT(reporter, iter.rgn()->isEmpty());
SkRegion::Cliperator clipIter(region, {0, 0, 100, 100});
REPORTER_ASSERT(reporter, clipIter.done());
auto cRect = clipIter.rect();
REPORTER_ASSERT(reporter, cRect.isEmpty());
REPORTER_ASSERT(reporter, SkIRect::MakeEmpty() == cRect);
SkRegion::Spanerator spanIter(region, 0, 0, 100);
int left = 0, right = 0;
REPORTER_ASSERT(reporter, !spanIter.next(&left, &right));
REPORTER_ASSERT(reporter, !left);
REPORTER_ASSERT(reporter, !right);
}
DEF_TEST(region_very_large, reporter) {
SkIRect clipBounds = {-45000, -45000, 45000, 45000};
REPORTER_ASSERT(reporter, SkScan::PathRequiresTiling(clipBounds));
// Create a path that is larger than the scan conversion limits of SkScan, which is internally
// used to convert a path to a region.
SkPath largePath = SkPath::RRect(SkRRect::MakeRectXY(SkRect::Make(clipBounds), 200.f, 200.f));
SkRegion largeRegion;
REPORTER_ASSERT(reporter, largeRegion.setPath(largePath, SkRegion{clipBounds}));
// The path should have been converted successfully, so the corners of clipBounds should not be
// contained due to the path's rounded corners.
REPORTER_ASSERT(reporter, !largeRegion.contains(-44995, -44995));
REPORTER_ASSERT(reporter, !largeRegion.contains(-44995, 44995));
REPORTER_ASSERT(reporter, !largeRegion.contains( 44995, -44995));
REPORTER_ASSERT(reporter, !largeRegion.contains( 44995, 44995));
// But these points should be within the rounded corners.
REPORTER_ASSERT(reporter, largeRegion.contains(-44600, -44600));
REPORTER_ASSERT(reporter, largeRegion.contains(-44600, 44600));
REPORTER_ASSERT(reporter, largeRegion.contains( 44600, -44600));
REPORTER_ASSERT(reporter, largeRegion.contains( 44600, 44600));
// Make another path shaped like a D, so two corners will have its large radii and the other two
// will be rectangular and thus clipped by the original region.
static const SkVector kLargeRadii[4] = { {0.f, 0.f}, // TL
{2000.f, 2000.f}, // TR
{2000.f, 2000.f}, // BR
{0.f, 0.f}}; // BL
SkRRect largeRRect;
largeRRect.setRectRadii(SkRect::Make(clipBounds), kLargeRadii);
REPORTER_ASSERT(reporter, largeRegion.setPath(SkPath::RRect(largeRRect), SkRegion{largeRegion}));
REPORTER_ASSERT(reporter, !largeRegion.contains(-44995, -44995));
REPORTER_ASSERT(reporter, !largeRegion.contains(-44995, 44995));
REPORTER_ASSERT(reporter, !largeRegion.contains( 44995, -44995));
REPORTER_ASSERT(reporter, !largeRegion.contains( 44995, 44995));
REPORTER_ASSERT(reporter, largeRegion.contains(-44600, -44600));
REPORTER_ASSERT(reporter, largeRegion.contains(-44600, 44600));
// Right side has been clipped by an even larger corner radii
REPORTER_ASSERT(reporter, !largeRegion.contains( 44600, -44600));
REPORTER_ASSERT(reporter, !largeRegion.contains( 44600, 44600));
// Now test that the very large path with a small clip also works
largePath = SkPath::RRect(SkRRect::MakeRectXY({0.f, 0.f, 45000.f, 45000.f}, 200.f, 200.f));
SkRegion smallRegion;
REPORTER_ASSERT(reporter, smallRegion.setPath(largePath, SkRegion{{0, 0, 500, 500}}));
REPORTER_ASSERT(reporter, !smallRegion.contains(5, 5));
REPORTER_ASSERT(reporter, smallRegion.contains(0, 499));
REPORTER_ASSERT(reporter, smallRegion.contains(499, 0));
REPORTER_ASSERT(reporter, smallRegion.contains(499, 499));
}
DEF_TEST(SkRegion_Iterator_StepsThroughAllScanlines, reporter) {
SkRegion rgn1;
rgn1.op({12, 10, 17, 20}, SkRegion::kUnion_Op);
rgn1.op({31, 10, 39, 25}, SkRegion::kUnion_Op);
rgn1.op({16, 30, 23, 40}, SkRegion::kUnion_Op);
int32_t buffer[32];
memset(&buffer, 0, 128);
size_t len = rgn1.writeToMemory(&buffer);
SkASSERT_RELEASE(len < 128);
SkRegion rgn2;
size_t len2 = rgn2.readFromMemory(&buffer, len);
REPORTER_ASSERT(reporter, len == len2);
// Make sure the serialized/deserialzed version is the same as the original.
for (const auto& rgn : {rgn1, rgn2}) {
SkRegion::Iterator iter(rgn);
// The first scanline strip starts at Y=10 and ends at Y=20.
// The first rectangle there starts at X=12 and ends at X=17
REPORTER_ASSERT(reporter, !iter.done());
REPORTER_ASSERT(reporter, iter.rect() == SkIRect::MakeLTRB(12, 10, 17, 20));
// There's a second rectangle in that section from X=31 to X=39.
iter.next();
REPORTER_ASSERT(reporter, !iter.done());
REPORTER_ASSERT(reporter, iter.rect() == SkIRect::MakeLTRB(31, 10, 39, 20));
// The next scanline strip continues at Y=20 and goes til Y=25
// The one and only rectangle here starts at X=31 and goes to X=39
iter.next();
REPORTER_ASSERT(reporter, !iter.done());
REPORTER_ASSERT(reporter, iter.rect() == SkIRect::MakeLTRB(31, 20, 39, 25));
// There's a jump to the final scanline strip from Y=30 to Y=40
// The one and only rectangle here starts at X=16 and goes to X=23
iter.next();
REPORTER_ASSERT(reporter, !iter.done());
REPORTER_ASSERT(reporter, iter.rect() == SkIRect::MakeLTRB(16, 30, 23, 40));
// Call next() -> no more rectangles
iter.next();
REPORTER_ASSERT(reporter, iter.done());
}
}
DEF_TEST(SkRegion_ReadFromMemory_ConsecutiveEmptySlices_Invalid, reporter) {
constexpr int32_t kSentinel = 0x7FFFFFFF;
const int32_t corrupt[] = {
42, // number of int32s in the RLE portion (after 7 metadata int32s)
0, 0, 100, 100, // bounds
12, // 12 spans (10 are empty)
2, // 2 rectangles
0, 5, // first stripe is from Y=0 to Y=5,
1, // 1 rectangle
0, 100, // from x = 0 to 100 (arbitrary)
kSentinel,
10, 0, kSentinel, // Empty from 5-10
20, 0, kSentinel, // Empty from 10-20...
30, 0, kSentinel,
40, 0, kSentinel,
50, 0, kSentinel,
60, 0, kSentinel,
70, 0, kSentinel,
80, 0, kSentinel,
90, 0, kSentinel,
95, 0, kSentinel,
100, 1, 20, 30, kSentinel, // one final real rectangle until Y=100
kSentinel, // final sentinal
};
SkRegion rgn;
size_t len = rgn.readFromMemory(&corrupt, sizeof(corrupt));
REPORTER_ASSERT(reporter, len == 0); // len == 0 means "could not read"
// When there was a buggy version of this, the following iteration caused a crash.
SkRegion::Iterator iter(rgn);
while (!iter.done()) {
iter.next();
}
}
DEF_TEST(SkRegion_ReadFromMemory_SingleEmptySlice_Valid, reporter) {
constexpr int32_t kSentinel = 0x7FFFFFFF;
const int32_t valid[] = {
15, // number of int32s in the RLE portion (after 7 metadata int32s)
0, 0, 100, 100, // bounds
3, // 3 spans (1 is empty)
2, // 2 rectangles
0, 5, // first stripe is from Y=0 to Y=5,
1, // 1 rectangle
0, 100, // from x = 0 to 100 (arbitrary)
kSentinel,
80, 0, kSentinel, // Empty from 5-80
100, 1, 20, 30, kSentinel, // one final real rectangle
kSentinel, // final sentinal
};
SkRegion rgn;
size_t len = rgn.readFromMemory(&valid, sizeof(valid));
REPORTER_ASSERT(reporter, len > 0); // len == 0 means "could not read"
// Make sure we don't read any memory we aren't supposed to.
SkRegion::Iterator iter(rgn);
while (!iter.done()) {
iter.next();
}
}
static bool test_rects(SkSpan<const SkIRect> rects) {
SkRegion rgn0, rgn1;
for (size_t i = 0; i < rects.size(); i++) {
rgn0.op(rects[i], SkRegion::kUnion_Op);
}
rgn1.setRects(rects);
if (rgn0 != rgn1) {
SkDebugf("\n");
for (size_t i = 0; i < rects.size(); i++) {
SkDebugf(" { %d, %d, %d, %d },\n",
rects[i].fLeft, rects[i].fTop,
rects[i].fRight, rects[i].fBottom);
}
SkDebugf("\n");
return false;
}
return true;
}
DEF_TEST(Region_setRects, reporter) {
auto test = [&](const char* name, SkSpan<const SkIRect> rects) {
skiatest::ReporterContext ctx(reporter, SkString(name));
REPORTER_ASSERT(reporter, test_rects(rects));
SkRegion rgn;
bool nonEmpty = rgn.setRects(rects);
REPORTER_ASSERT(reporter, nonEmpty == !rgn.isEmpty());
};
test("0_rects", {});
const SkIRect r1[] = {{0, 0, 10, 10}};
test("1_rect", r1);
const SkIRect r2[] = {{0, 0, 1, 1}, {2, 2, 3, 3}};
test("2_rects", r2);
const SkIRect r3[] = {{0, 0, 1, 1}, {2, 2, 3, 3}, {4, 4, 5, 5}};
test("3_rects", r3);
const SkIRect r4[] = {
{ 0, 0, 1, 2 },
{ 2, 1, 3, 3 },
{ 4, 0, 5, 1 },
{ 6, 0, 7, 4 },
};
test("4_rects", r4);
const SkIRect rOverlap[] = {{0, 0, 10, 10}, {5, 0, 15, 10}, {0, 5, 10, 15}, {5, 5, 15, 15}};
test("overlapping_rects", rOverlap);
const SkIRect rEmpty[] = {{0, 0, 0, 0}, {10, 10, 10, 10}};
test("empty_rects", rEmpty);
SkIRect grid[100];
for (int y = 0; y < 10; ++y) {
for (int x = 0; x < 10; ++x) {
grid[y * 10 + x] = SkIRect::MakeXYWH(x * 20, y * 20, 10, 10);
}
}
test("100_rects_grid", grid);
}