/*
* Copyright 2015 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "SkMatrix.h"
#include "SkPath.h"
#include "SkPathRef.h"
#include "SkRRect.h"
#include "Test.h"
static SkRRect path_contains_rrect(skiatest::Reporter* reporter, const SkPath& path,
SkPath::Direction* dir, unsigned* start) {
SkRRect out;
REPORTER_ASSERT(reporter, path.isRRect(&out, dir, start));
SkPath recreatedPath;
recreatedPath.addRRect(out, *dir, *start);
REPORTER_ASSERT(reporter, path == recreatedPath);
// Test that rotations/mirrors of the rrect path are still rrect paths and the returned
// parameters for the transformed paths are correct.
static const SkMatrix kMatrices[] = {
SkMatrix::MakeScale(1, 1),
SkMatrix::MakeScale(-1, 1),
SkMatrix::MakeScale(1, -1),
SkMatrix::MakeScale(-1, -1),
};
for (auto& m : kMatrices) {
SkPath xformed;
path.transform(m, &xformed);
SkRRect xrr = SkRRect::MakeRect(SkRect::MakeEmpty());
SkPath::Direction xd = SkPath::kCCW_Direction;
unsigned xs = ~0U;
REPORTER_ASSERT(reporter, xformed.isRRect(&xrr, &xd, &xs));
recreatedPath.reset();
recreatedPath.addRRect(xrr, xd, xs);
REPORTER_ASSERT(reporter, recreatedPath == xformed);
}
return out;
}
static SkRRect inner_path_contains_rrect(skiatest::Reporter* reporter, const SkRRect& in,
SkPath::Direction dir, unsigned start) {
switch (in.getType()) {
case SkRRect::kEmpty_Type:
case SkRRect::kRect_Type:
case SkRRect::kOval_Type:
return in;
default:
break;
}
SkPath path;
path.addRRect(in, dir, start);
SkPath::Direction outDir;
unsigned outStart;
SkRRect rrect = path_contains_rrect(reporter, path, &outDir, &outStart);
REPORTER_ASSERT(reporter, outDir == dir && outStart == start);
return rrect;
}
static void path_contains_rrect_check(skiatest::Reporter* reporter, const SkRRect& in,
SkPath::Direction dir, unsigned start) {
SkRRect out = inner_path_contains_rrect(reporter, in, dir, start);
if (in != out) {
SkDebugf("");
}
REPORTER_ASSERT(reporter, in == out);
}
static void path_contains_rrect_nocheck(skiatest::Reporter* reporter, const SkRRect& in,
SkPath::Direction dir, unsigned start) {
SkRRect out = inner_path_contains_rrect(reporter, in, dir, start);
if (in == out) {
SkDebugf("");
}
}
static void path_contains_rrect_check(skiatest::Reporter* reporter, const SkRect& r,
SkVector v[4], SkPath::Direction dir, unsigned start) {
SkRRect rrect;
rrect.setRectRadii(r, v);
path_contains_rrect_check(reporter, rrect, dir, start);
}
class ForceIsRRect_Private {
public:
ForceIsRRect_Private(SkPath* path, SkPath::Direction dir, unsigned start) {
path->fPathRef->setIsRRect(true, dir == SkPath::kCCW_Direction, start);
}
};
static void force_path_contains_rrect(skiatest::Reporter* reporter, SkPath& path,
SkPath::Direction dir, unsigned start) {
ForceIsRRect_Private force_rrect(&path, dir, start);
SkPath::Direction outDir;
unsigned outStart;
path_contains_rrect(reporter, path, &outDir, &outStart);
REPORTER_ASSERT(reporter, outDir == dir && outStart == start);
}
static void test_undetected_paths(skiatest::Reporter* reporter) {
// We use a dummy path to get the exact conic weight used by SkPath for a circular arc. This
// allows our local, hand-crafted, artisanal round rect paths below to exactly match the
// factory made corporate paths produced by SkPath.
SkPath dummyPath;
dummyPath.addCircle(0, 0, 10);
SkPath::RawIter iter(dummyPath);
SkPoint dummyPts[4];
SkPath::Verb v = iter.next(dummyPts);
REPORTER_ASSERT(reporter, SkPath::kMove_Verb == v);
v = iter.next(dummyPts);
REPORTER_ASSERT(reporter, SkPath::kConic_Verb == v);
const SkScalar weight = iter.conicWeight();
SkPath path;
path.moveTo(0, 62.5f);
path.lineTo(0, 3.5f);
path.conicTo(0, 0, 3.5f, 0, weight);
path.lineTo(196.5f, 0);
path.conicTo(200, 0, 200, 3.5f, weight);
path.lineTo(200, 62.5f);
path.conicTo(200, 66, 196.5f, 66, weight);
path.lineTo(3.5f, 66);
path.conicTo(0, 66, 0, 62.5, weight);
path.close();
force_path_contains_rrect(reporter, path, SkPath::kCW_Direction, 6);
path.reset();
path.moveTo(0, 81.5f);
path.lineTo(0, 3.5f);
path.conicTo(0, 0, 3.5f, 0, weight);
path.lineTo(149.5, 0);
path.conicTo(153, 0, 153, 3.5f, weight);
path.lineTo(153, 81.5f);
path.conicTo(153, 85, 149.5f, 85, weight);
path.lineTo(3.5f, 85);
path.conicTo(0, 85, 0, 81.5f, weight);
path.close();
force_path_contains_rrect(reporter, path, SkPath::kCW_Direction, 6);
path.reset();
path.moveTo(14, 1189);
path.lineTo(14, 21);
path.conicTo(14, 14, 21, 14, weight);
path.lineTo(1363, 14);
path.conicTo(1370, 14, 1370, 21, weight);
path.lineTo(1370, 1189);
path.conicTo(1370, 1196, 1363, 1196, weight);
path.lineTo(21, 1196);
path.conicTo(14, 1196, 14, 1189, weight);
path.close();
force_path_contains_rrect(reporter, path, SkPath::kCW_Direction, 6);
path.reset();
path.moveTo(14, 1743);
path.lineTo(14, 21);
path.conicTo(14, 14, 21, 14, weight);
path.lineTo(1363, 14);
path.conicTo(1370, 14, 1370, 21, weight);
path.lineTo(1370, 1743);
path.conicTo(1370, 1750, 1363, 1750, weight);
path.lineTo(21, 1750);
path.conicTo(14, 1750, 14, 1743, weight);
path.close();
force_path_contains_rrect(reporter, path, SkPath::kCW_Direction, 6);
}
static const SkScalar kWidth = 100.0f;
static const SkScalar kHeight = 100.0f;
static void test_tricky_radii(skiatest::Reporter* reporter) {
for (auto dir : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) {
for (int start = 0; start < 8; ++start) {
{
// crbug.com/458522
SkRRect rr;
const SkRect bounds = { 3709, 3709, 3709 + 7402, 3709 + 29825 };
const SkScalar rad = 12814;
const SkVector vec[] = { { rad, rad }, { 0, rad }, { rad, rad }, { 0, rad } };
rr.setRectRadii(bounds, vec);
path_contains_rrect_check(reporter, rr, dir, start);
}
{
// crbug.com//463920
SkRect r = SkRect::MakeLTRB(0, 0, 1009, 33554432.0);
SkVector radii[4] = {
{ 13.0f, 8.0f }, { 170.0f, 2.0 }, { 256.0f, 33554432.0 }, { 110.0f, 5.0f }
};
SkRRect rr;
rr.setRectRadii(r, radii);
path_contains_rrect_nocheck(reporter, rr, dir, start);
}
}
}
}
static void test_empty_crbug_458524(skiatest::Reporter* reporter) {
for (auto dir : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) {
for (int start = 0; start < 8; ++start) {
SkRRect rr;
const SkRect bounds = { 3709, 3709, 3709 + 7402, 3709 + 29825 };
const SkScalar rad = 40;
rr.setRectXY(bounds, rad, rad);
path_contains_rrect_check(reporter, rr, dir, start);
SkRRect other;
SkMatrix matrix;
matrix.setScale(0, 1);
rr.transform(matrix, &other);
path_contains_rrect_check(reporter, rr, dir, start);
}
}
}
static void test_inset(skiatest::Reporter* reporter) {
for (auto dir : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) {
for (int start = 0; start < 8; ++start) {
SkRRect rr, rr2;
SkRect r = { 0, 0, 100, 100 };
rr.setRect(r);
rr.inset(-20, -20, &rr2);
path_contains_rrect_check(reporter, rr, dir, start);
rr.inset(20, 20, &rr2);
path_contains_rrect_check(reporter, rr, dir, start);
rr.inset(r.width()/2, r.height()/2, &rr2);
path_contains_rrect_check(reporter, rr, dir, start);
rr.setRectXY(r, 20, 20);
rr.inset(19, 19, &rr2);
path_contains_rrect_check(reporter, rr, dir, start);
rr.inset(20, 20, &rr2);
path_contains_rrect_check(reporter, rr, dir, start);
}
}
}
static void test_9patch_rrect(skiatest::Reporter* reporter,
const SkRect& rect,
SkScalar l, SkScalar t, SkScalar r, SkScalar b,
bool checkRadii) {
for (auto dir : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) {
for (int start = 0; start < 8; ++start) {
SkRRect rr;
rr.setNinePatch(rect, l, t, r, b);
if (checkRadii) {
path_contains_rrect_check(reporter, rr, dir, start);
} else {
path_contains_rrect_nocheck(reporter, rr, dir, start);
}
SkRRect rr2; // construct the same RR using the most general set function
SkVector radii[4] = { { l, t }, { r, t }, { r, b }, { l, b } };
rr2.setRectRadii(rect, radii);
if (checkRadii) {
path_contains_rrect_check(reporter, rr, dir, start);
} else {
path_contains_rrect_nocheck(reporter, rr, dir, start);
}
}
}
}
// Test out the basic API entry points
static void test_round_rect_basic(skiatest::Reporter* reporter) {
for (auto dir : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) {
for (int start = 0; start < 8; ++start) {
//----
SkRect rect = SkRect::MakeLTRB(0, 0, kWidth, kHeight);
SkRRect rr1;
rr1.setRect(rect);
path_contains_rrect_check(reporter, rr1, dir, start);
SkRRect rr1_2; // construct the same RR using the most general set function
SkVector rr1_2_radii[4] = { { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 } };
rr1_2.setRectRadii(rect, rr1_2_radii);
path_contains_rrect_check(reporter, rr1_2, dir, start);
SkRRect rr1_3; // construct the same RR using the nine patch set function
rr1_3.setNinePatch(rect, 0, 0, 0, 0);
path_contains_rrect_check(reporter, rr1_2, dir, start);
//----
SkPoint halfPoint = { SkScalarHalf(kWidth), SkScalarHalf(kHeight) };
SkRRect rr2;
rr2.setOval(rect);
path_contains_rrect_check(reporter, rr2, dir, start);
SkRRect rr2_2; // construct the same RR using the most general set function
SkVector rr2_2_radii[4] = { { halfPoint.fX, halfPoint.fY },
{ halfPoint.fX, halfPoint.fY },
{ halfPoint.fX, halfPoint.fY },
{ halfPoint.fX, halfPoint.fY } };
rr2_2.setRectRadii(rect, rr2_2_radii);
path_contains_rrect_check(reporter, rr2_2, dir, start);
SkRRect rr2_3; // construct the same RR using the nine patch set function
rr2_3.setNinePatch(rect, halfPoint.fX, halfPoint.fY, halfPoint.fX, halfPoint.fY);
path_contains_rrect_check(reporter, rr2_3, dir, start);
//----
SkPoint p = { 5, 5 };
SkRRect rr3;
rr3.setRectXY(rect, p.fX, p.fY);
path_contains_rrect_check(reporter, rr3, dir, start);
SkRRect rr3_2; // construct the same RR using the most general set function
SkVector rr3_2_radii[4] = { { 5, 5 }, { 5, 5 }, { 5, 5 }, { 5, 5 } };
rr3_2.setRectRadii(rect, rr3_2_radii);
path_contains_rrect_check(reporter, rr3_2, dir, start);
SkRRect rr3_3; // construct the same RR using the nine patch set function
rr3_3.setNinePatch(rect, 5, 5, 5, 5);
path_contains_rrect_check(reporter, rr3_3, dir, start);
//----
test_9patch_rrect(reporter, rect, 10, 9, 8, 7, true);
{
// Test out the rrect from skia:3466
SkRect rect2 = SkRect::MakeLTRB(0.358211994f, 0.755430222f, 0.872866154f,
0.806214333f);
test_9patch_rrect(reporter,
rect2,
0.926942348f, 0.642850280f, 0.529063463f, 0.587844372f,
false);
}
//----
SkPoint radii2[4] = { { 0, 0 }, { 0, 0 }, { 50, 50 }, { 20, 50 } };
SkRRect rr5;
rr5.setRectRadii(rect, radii2);
path_contains_rrect_check(reporter, rr5, dir, start);
}
}
}
// Test out the cases when the RR degenerates to a rect
static void test_round_rect_rects(skiatest::Reporter* reporter) {
for (auto dir : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) {
for (int start = 0; start < 8; ++start) {
//----
SkRect rect = SkRect::MakeLTRB(0, 0, kWidth, kHeight);
SkRRect rr1;
rr1.setRectXY(rect, 0, 0);
path_contains_rrect_check(reporter, rr1, dir, start);
//----
SkPoint radii[4] = { { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 } };
SkRRect rr2;
rr2.setRectRadii(rect, radii);
path_contains_rrect_check(reporter, rr2, dir, start);
//----
SkPoint radii2[4] = { { 0, 0 }, { 20, 20 }, { 50, 50 }, { 20, 50 } };
SkRRect rr3;
rr3.setRectRadii(rect, radii2);
path_contains_rrect_check(reporter, rr3, dir, start);
}
}
}
// Test out the cases when the RR degenerates to an oval
static void test_round_rect_ovals(skiatest::Reporter* reporter) {
for (auto dir : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) {
for (int start = 0; start < 8; ++start) {
//----
SkRect rect = SkRect::MakeLTRB(0, 0, kWidth, kHeight);
SkRRect rr1;
rr1.setRectXY(rect, SkScalarHalf(kWidth), SkScalarHalf(kHeight));
path_contains_rrect_check(reporter, rr1, dir, start);
}
}
}
// Test out the non-degenerate RR cases
static void test_round_rect_general(skiatest::Reporter* reporter) {
for (auto dir : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) {
for (int start = 0; start < 8; ++start) {
//----
SkRect rect = SkRect::MakeLTRB(0, 0, kWidth, kHeight);
SkRRect rr1;
rr1.setRectXY(rect, 20, 20);
path_contains_rrect_check(reporter, rr1, dir, start);
//----
SkPoint radii[4] = { { 0, 0 }, { 20, 20 }, { 50, 50 }, { 20, 50 } };
SkRRect rr2;
rr2.setRectRadii(rect, radii);
path_contains_rrect_check(reporter, rr2, dir, start);
}
}
}
static void test_round_rect_iffy_parameters(skiatest::Reporter* reporter) {
for (auto dir : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) {
for (int start = 0; start < 8; ++start) {
SkRect rect = SkRect::MakeLTRB(0, 0, kWidth, kHeight);
SkPoint radii[4] = { { 50, 100 }, { 100, 50 }, { 50, 100 }, { 100, 50 } };
SkRRect rr1;
rr1.setRectRadii(rect, radii);
path_contains_rrect_nocheck(reporter, rr1, dir, start);
}
}
}
static void set_radii(SkVector radii[4], int index, float rad) {
sk_bzero(radii, sizeof(SkVector) * 4);
radii[index].set(rad, rad);
}
static void test_skbug_3239(skiatest::Reporter* reporter) {
const float min = SkBits2Float(0xcb7f16c8); /* -16717512.000000 */
const float max = SkBits2Float(0x4b7f1c1d); /* 16718877.000000 */
const float big = SkBits2Float(0x4b7f1bd7); /* 16718807.000000 */
const float rad = 33436320;
const SkRect rectx = SkRect::MakeLTRB(min, min, max, big);
const SkRect recty = SkRect::MakeLTRB(min, min, big, max);
for (auto dir : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) {
for (int start = 0; start < 8; ++start) {
SkVector radii[4];
for (int i = 0; i < 4; ++i) {
set_radii(radii, i, rad);
path_contains_rrect_check(reporter, rectx, radii, dir, start);
path_contains_rrect_check(reporter, recty, radii, dir, start);
}
}
}
}
static void test_mix(skiatest::Reporter* reporter) {
for (auto dir : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) {
for (int start = 0; start < 8; ++start) {
// Test out mixed degenerate and non-degenerate geometry with Conics
const SkVector radii[4] = { { 0, 0 }, { 0, 0 }, { 0, 0 }, { 100, 100 } };
SkRect r = SkRect::MakeWH(100, 100);
SkRRect rr;
rr.setRectRadii(r, radii);
path_contains_rrect_check(reporter, rr, dir, start);
}
}
}
DEF_TEST(RoundRectInPath, reporter) {
test_tricky_radii(reporter);
test_empty_crbug_458524(reporter);
test_inset(reporter);
test_round_rect_basic(reporter);
test_round_rect_rects(reporter);
test_round_rect_ovals(reporter);
test_round_rect_general(reporter);
test_undetected_paths(reporter);
test_round_rect_iffy_parameters(reporter);
test_skbug_3239(reporter);
test_mix(reporter);
}