Home | History | Annotate | Download | only in tests
      1 /*
      2  * Copyright 2015 Google Inc.
      3  *
      4  * Use of this source code is governed by a BSD-style license that can be
      5  * found in the LICENSE file.
      6  */
      7 
      8 #include "SkMatrix.h"
      9 #include "SkPath.h"
     10 #include "SkPathPriv.h"
     11 #include "SkRRect.h"
     12 #include "Test.h"
     13 
     14 static SkRRect path_contains_rrect(skiatest::Reporter* reporter, const SkPath& path,
     15                                    SkPath::Direction* dir, unsigned* start) {
     16     SkRRect out;
     17     REPORTER_ASSERT(reporter, SkPathPriv::IsRRect(path, &out, dir, start));
     18     SkPath recreatedPath;
     19     recreatedPath.addRRect(out, *dir, *start);
     20     REPORTER_ASSERT(reporter, path == recreatedPath);
     21     // Test that rotations/mirrors of the rrect path are still rrect paths and the returned
     22     // parameters for the transformed paths are correct.
     23     static const SkMatrix kMatrices[] = {
     24         SkMatrix::MakeScale(1, 1),
     25         SkMatrix::MakeScale(-1, 1),
     26         SkMatrix::MakeScale(1, -1),
     27         SkMatrix::MakeScale(-1, -1),
     28     };
     29     for (auto& m : kMatrices) {
     30         SkPath xformed;
     31         path.transform(m, &xformed);
     32         SkRRect xrr = SkRRect::MakeRect(SkRect::MakeEmpty());
     33         SkPath::Direction xd = SkPath::kCCW_Direction;
     34         unsigned xs = ~0U;
     35         REPORTER_ASSERT(reporter, SkPathPriv::IsRRect(xformed, &xrr, &xd, &xs));
     36         recreatedPath.reset();
     37         recreatedPath.addRRect(xrr, xd, xs);
     38         REPORTER_ASSERT(reporter, recreatedPath == xformed);
     39     }
     40     return out;
     41 }
     42 
     43 static SkRRect inner_path_contains_rrect(skiatest::Reporter* reporter, const SkRRect& in,
     44                                          SkPath::Direction dir, unsigned start) {
     45     switch (in.getType()) {
     46         case SkRRect::kEmpty_Type:
     47         case SkRRect::kRect_Type:
     48         case SkRRect::kOval_Type:
     49             return in;
     50         default:
     51             break;
     52     }
     53     SkPath path;
     54     path.addRRect(in, dir, start);
     55     SkPath::Direction outDir;
     56     unsigned outStart;
     57     SkRRect rrect = path_contains_rrect(reporter, path, &outDir, &outStart);
     58     REPORTER_ASSERT(reporter, outDir == dir && outStart == start);
     59     return rrect;
     60 }
     61 
     62 static void path_contains_rrect_check(skiatest::Reporter* reporter, const SkRRect& in,
     63                                       SkPath::Direction dir, unsigned start) {
     64     SkRRect out = inner_path_contains_rrect(reporter, in, dir, start);
     65     if (in != out) {
     66         SkDebugf("");
     67     }
     68     REPORTER_ASSERT(reporter, in == out);
     69 }
     70 
     71 static void path_contains_rrect_nocheck(skiatest::Reporter* reporter, const SkRRect& in,
     72                                         SkPath::Direction dir, unsigned start) {
     73     SkRRect out = inner_path_contains_rrect(reporter, in, dir, start);
     74     if (in == out) {
     75         SkDebugf("");
     76     }
     77 }
     78 
     79 static void path_contains_rrect_check(skiatest::Reporter* reporter, const SkRect& r,
     80         SkVector v[4], SkPath::Direction dir, unsigned start) {
     81     SkRRect rrect;
     82     rrect.setRectRadii(r, v);
     83     path_contains_rrect_check(reporter, rrect, dir, start);
     84 }
     85 
     86 class ForceIsRRect_Private {
     87 public:
     88     ForceIsRRect_Private(SkPath* path, SkPath::Direction dir, unsigned start) {
     89         path->fPathRef->setIsRRect(true, dir == SkPath::kCCW_Direction, start);
     90     }
     91 };
     92 
     93 static void force_path_contains_rrect(skiatest::Reporter* reporter, SkPath& path,
     94                                       SkPath::Direction dir, unsigned start) {
     95     ForceIsRRect_Private force_rrect(&path, dir, start);
     96     SkPath::Direction outDir;
     97     unsigned outStart;
     98     path_contains_rrect(reporter, path, &outDir, &outStart);
     99     REPORTER_ASSERT(reporter, outDir == dir && outStart == start);
    100 }
    101 
    102 static void test_undetected_paths(skiatest::Reporter* reporter) {
    103     // We use a dummy path to get the exact conic weight used by SkPath for a circular arc. This
    104     // allows our local, hand-crafted, artisanal round rect paths below to exactly match the
    105     // factory made corporate paths produced by SkPath.
    106     SkPath dummyPath;
    107     dummyPath.addCircle(0, 0, 10);
    108     SkPath::RawIter iter(dummyPath);
    109     SkPoint dummyPts[4];
    110     SkPath::Verb v = iter.next(dummyPts);
    111     REPORTER_ASSERT(reporter, SkPath::kMove_Verb == v);
    112     v = iter.next(dummyPts);
    113     REPORTER_ASSERT(reporter, SkPath::kConic_Verb == v);
    114     const SkScalar weight = iter.conicWeight();
    115 
    116     SkPath path;
    117     path.moveTo(0, 62.5f);
    118     path.lineTo(0, 3.5f);
    119     path.conicTo(0, 0, 3.5f, 0, weight);
    120     path.lineTo(196.5f, 0);
    121     path.conicTo(200, 0, 200, 3.5f, weight);
    122     path.lineTo(200, 62.5f);
    123     path.conicTo(200, 66, 196.5f, 66, weight);
    124     path.lineTo(3.5f, 66);
    125     path.conicTo(0, 66, 0, 62.5, weight);
    126     path.close();
    127     force_path_contains_rrect(reporter, path, SkPath::kCW_Direction, 6);
    128 
    129     path.reset();
    130     path.moveTo(0, 81.5f);
    131     path.lineTo(0, 3.5f);
    132     path.conicTo(0, 0, 3.5f, 0, weight);
    133     path.lineTo(149.5, 0);
    134     path.conicTo(153, 0, 153, 3.5f, weight);
    135     path.lineTo(153, 81.5f);
    136     path.conicTo(153, 85, 149.5f, 85, weight);
    137     path.lineTo(3.5f, 85);
    138     path.conicTo(0, 85, 0, 81.5f, weight);
    139     path.close();
    140     force_path_contains_rrect(reporter, path, SkPath::kCW_Direction, 6);
    141 
    142     path.reset();
    143     path.moveTo(14, 1189);
    144     path.lineTo(14, 21);
    145     path.conicTo(14, 14, 21, 14, weight);
    146     path.lineTo(1363, 14);
    147     path.conicTo(1370, 14, 1370, 21, weight);
    148     path.lineTo(1370, 1189);
    149     path.conicTo(1370, 1196, 1363, 1196, weight);
    150     path.lineTo(21, 1196);
    151     path.conicTo(14, 1196, 14, 1189, weight);
    152     path.close();
    153     force_path_contains_rrect(reporter, path, SkPath::kCW_Direction, 6);
    154 
    155     path.reset();
    156     path.moveTo(14, 1743);
    157     path.lineTo(14, 21);
    158     path.conicTo(14, 14, 21, 14, weight);
    159     path.lineTo(1363, 14);
    160     path.conicTo(1370, 14, 1370, 21, weight);
    161     path.lineTo(1370, 1743);
    162     path.conicTo(1370, 1750, 1363, 1750, weight);
    163     path.lineTo(21, 1750);
    164     path.conicTo(14, 1750, 14, 1743, weight);
    165     path.close();
    166     force_path_contains_rrect(reporter, path, SkPath::kCW_Direction, 6);
    167 }
    168 
    169 static const SkScalar kWidth = 100.0f;
    170 static const SkScalar kHeight = 100.0f;
    171 
    172 static void test_tricky_radii(skiatest::Reporter* reporter) {
    173     for (auto dir : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) {
    174         for (int start = 0; start < 8; ++start) {
    175             {
    176                 // crbug.com/458522
    177                 SkRRect rr;
    178                 const SkRect bounds = { 3709, 3709, 3709 + 7402, 3709 + 29825 };
    179                 const SkScalar rad = 12814;
    180                 const SkVector vec[] = { { rad, rad }, { 0, rad }, { rad, rad }, { 0, rad } };
    181                 rr.setRectRadii(bounds, vec);
    182                 path_contains_rrect_check(reporter, rr, dir, start);
    183             }
    184 
    185             {
    186                 // crbug.com//463920
    187                 SkRect r = SkRect::MakeLTRB(0, 0, 1009, 33554432.0);
    188                 SkVector radii[4] = {
    189                     { 13.0f, 8.0f }, { 170.0f, 2.0 }, { 256.0f, 33554432.0 }, { 110.0f, 5.0f }
    190                 };
    191                 SkRRect rr;
    192                 rr.setRectRadii(r, radii);
    193                 path_contains_rrect_nocheck(reporter, rr, dir, start);
    194             }
    195         }
    196     }
    197 }
    198 
    199 static void test_empty_crbug_458524(skiatest::Reporter* reporter) {
    200     for (auto dir : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) {
    201         for (int start = 0; start < 8; ++start) {
    202             SkRRect rr;
    203             const SkRect bounds = { 3709, 3709, 3709 + 7402, 3709 + 29825 };
    204             const SkScalar rad = 40;
    205             rr.setRectXY(bounds, rad, rad);
    206             path_contains_rrect_check(reporter, rr, dir, start);
    207 
    208             SkRRect other;
    209             SkMatrix matrix;
    210             matrix.setScale(0, 1);
    211             rr.transform(matrix, &other);
    212             path_contains_rrect_check(reporter, rr, dir, start);
    213         }
    214     }
    215 }
    216 
    217 static void test_inset(skiatest::Reporter* reporter) {
    218     for (auto dir : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) {
    219         for (int start = 0; start < 8; ++start) {
    220             SkRRect rr, rr2;
    221             SkRect r = { 0, 0, 100, 100 };
    222 
    223             rr.setRect(r);
    224             rr.inset(-20, -20, &rr2);
    225             path_contains_rrect_check(reporter, rr, dir, start);
    226 
    227             rr.inset(20, 20, &rr2);
    228             path_contains_rrect_check(reporter, rr, dir, start);
    229 
    230             rr.inset(r.width()/2, r.height()/2, &rr2);
    231             path_contains_rrect_check(reporter, rr, dir, start);
    232 
    233             rr.setRectXY(r, 20, 20);
    234             rr.inset(19, 19, &rr2);
    235             path_contains_rrect_check(reporter, rr, dir, start);
    236             rr.inset(20, 20, &rr2);
    237             path_contains_rrect_check(reporter, rr, dir, start);
    238         }
    239     }
    240 }
    241 
    242 
    243 static void test_9patch_rrect(skiatest::Reporter* reporter,
    244                               const SkRect& rect,
    245                               SkScalar l, SkScalar t, SkScalar r, SkScalar b,
    246                               bool checkRadii) {
    247     for (auto dir : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) {
    248         for (int start = 0; start < 8; ++start) {
    249             SkRRect rr;
    250             rr.setNinePatch(rect, l, t, r, b);
    251             if (checkRadii) {
    252                 path_contains_rrect_check(reporter, rr, dir, start);
    253             } else {
    254                 path_contains_rrect_nocheck(reporter, rr, dir, start);
    255             }
    256 
    257             SkRRect rr2; // construct the same RR using the most general set function
    258             SkVector radii[4] = { { l, t }, { r, t }, { r, b }, { l, b } };
    259             rr2.setRectRadii(rect, radii);
    260             if (checkRadii) {
    261                 path_contains_rrect_check(reporter, rr, dir, start);
    262             } else {
    263                 path_contains_rrect_nocheck(reporter, rr, dir, start);
    264             }
    265         }
    266     }
    267 }
    268 
    269 // Test out the basic API entry points
    270 static void test_round_rect_basic(skiatest::Reporter* reporter) {
    271     for (auto dir : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) {
    272         for (int start = 0; start < 8; ++start) {
    273             //----
    274             SkRect rect = SkRect::MakeLTRB(0, 0, kWidth, kHeight);
    275 
    276             SkRRect rr1;
    277             rr1.setRect(rect);
    278             path_contains_rrect_check(reporter, rr1, dir, start);
    279 
    280             SkRRect rr1_2; // construct the same RR using the most general set function
    281             SkVector rr1_2_radii[4] = { { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 } };
    282             rr1_2.setRectRadii(rect, rr1_2_radii);
    283             path_contains_rrect_check(reporter, rr1_2, dir, start);
    284             SkRRect rr1_3;  // construct the same RR using the nine patch set function
    285             rr1_3.setNinePatch(rect, 0, 0, 0, 0);
    286             path_contains_rrect_check(reporter, rr1_2, dir, start);
    287 
    288             //----
    289             SkPoint halfPoint = { SkScalarHalf(kWidth), SkScalarHalf(kHeight) };
    290             SkRRect rr2;
    291             rr2.setOval(rect);
    292             path_contains_rrect_check(reporter, rr2, dir, start);
    293 
    294             SkRRect rr2_2;  // construct the same RR using the most general set function
    295             SkVector rr2_2_radii[4] = { { halfPoint.fX, halfPoint.fY },
    296                                         { halfPoint.fX, halfPoint.fY },
    297                                         { halfPoint.fX, halfPoint.fY },
    298                                         { halfPoint.fX, halfPoint.fY } };
    299             rr2_2.setRectRadii(rect, rr2_2_radii);
    300             path_contains_rrect_check(reporter, rr2_2, dir, start);
    301             SkRRect rr2_3;  // construct the same RR using the nine patch set function
    302             rr2_3.setNinePatch(rect, halfPoint.fX, halfPoint.fY, halfPoint.fX, halfPoint.fY);
    303             path_contains_rrect_check(reporter, rr2_3, dir, start);
    304 
    305             //----
    306             SkPoint p = { 5, 5 };
    307             SkRRect rr3;
    308             rr3.setRectXY(rect, p.fX, p.fY);
    309             path_contains_rrect_check(reporter, rr3, dir, start);
    310 
    311             SkRRect rr3_2; // construct the same RR using the most general set function
    312             SkVector rr3_2_radii[4] = { { 5, 5 }, { 5, 5 }, { 5, 5 }, { 5, 5 } };
    313             rr3_2.setRectRadii(rect, rr3_2_radii);
    314             path_contains_rrect_check(reporter, rr3_2, dir, start);
    315             SkRRect rr3_3;  // construct the same RR using the nine patch set function
    316             rr3_3.setNinePatch(rect, 5, 5, 5, 5);
    317             path_contains_rrect_check(reporter, rr3_3, dir, start);
    318 
    319             //----
    320             test_9patch_rrect(reporter, rect, 10, 9, 8, 7, true);
    321 
    322             {
    323                 // Test out the rrect from skia:3466
    324                 SkRect rect2 = SkRect::MakeLTRB(0.358211994f, 0.755430222f, 0.872866154f,
    325                                                 0.806214333f);
    326 
    327                 test_9patch_rrect(reporter,
    328                                   rect2,
    329                                   0.926942348f, 0.642850280f, 0.529063463f, 0.587844372f,
    330                                   false);
    331             }
    332 
    333             //----
    334             SkPoint radii2[4] = { { 0, 0 }, { 0, 0 }, { 50, 50 }, { 20, 50 } };
    335 
    336             SkRRect rr5;
    337             rr5.setRectRadii(rect, radii2);
    338             path_contains_rrect_check(reporter, rr5, dir, start);
    339         }
    340     }
    341 }
    342 
    343 // Test out the cases when the RR degenerates to a rect
    344 static void test_round_rect_rects(skiatest::Reporter* reporter) {
    345     for (auto dir : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) {
    346         for (int start = 0; start < 8; ++start) {
    347             //----
    348             SkRect rect = SkRect::MakeLTRB(0, 0, kWidth, kHeight);
    349             SkRRect rr1;
    350             rr1.setRectXY(rect, 0, 0);
    351 
    352             path_contains_rrect_check(reporter, rr1, dir, start);
    353 
    354             //----
    355             SkPoint radii[4] = { { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 } };
    356 
    357             SkRRect rr2;
    358             rr2.setRectRadii(rect, radii);
    359 
    360             path_contains_rrect_check(reporter, rr2, dir, start);
    361 
    362             //----
    363             SkPoint radii2[4] = { { 0, 0 }, { 20, 20 }, { 50, 50 }, { 20, 50 } };
    364 
    365             SkRRect rr3;
    366             rr3.setRectRadii(rect, radii2);
    367             path_contains_rrect_check(reporter, rr3, dir, start);
    368         }
    369     }
    370 }
    371 
    372 // Test out the cases when the RR degenerates to an oval
    373 static void test_round_rect_ovals(skiatest::Reporter* reporter) {
    374     for (auto dir : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) {
    375         for (int start = 0; start < 8; ++start) {
    376             //----
    377             SkRect rect = SkRect::MakeLTRB(0, 0, kWidth, kHeight);
    378             SkRRect rr1;
    379             rr1.setRectXY(rect, SkScalarHalf(kWidth), SkScalarHalf(kHeight));
    380 
    381             path_contains_rrect_check(reporter, rr1, dir, start);
    382         }
    383     }
    384 }
    385 
    386 // Test out the non-degenerate RR cases
    387 static void test_round_rect_general(skiatest::Reporter* reporter) {
    388     for (auto dir : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) {
    389         for (int start = 0; start < 8; ++start) {
    390             //----
    391             SkRect rect = SkRect::MakeLTRB(0, 0, kWidth, kHeight);
    392             SkRRect rr1;
    393             rr1.setRectXY(rect, 20, 20);
    394 
    395             path_contains_rrect_check(reporter, rr1, dir, start);
    396 
    397             //----
    398             SkPoint radii[4] = { { 0, 0 }, { 20, 20 }, { 50, 50 }, { 20, 50 } };
    399 
    400             SkRRect rr2;
    401             rr2.setRectRadii(rect, radii);
    402 
    403             path_contains_rrect_check(reporter, rr2, dir, start);
    404         }
    405     }
    406 }
    407 
    408 static void test_round_rect_iffy_parameters(skiatest::Reporter* reporter) {
    409     for (auto dir : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) {
    410         for (int start = 0; start < 8; ++start) {
    411             SkRect rect = SkRect::MakeLTRB(0, 0, kWidth, kHeight);
    412             SkPoint radii[4] = { { 50, 100 }, { 100, 50 }, { 50, 100 }, { 100, 50 } };
    413             SkRRect rr1;
    414             rr1.setRectRadii(rect, radii);
    415             path_contains_rrect_nocheck(reporter, rr1, dir, start);
    416         }
    417     }
    418 }
    419 
    420 static void set_radii(SkVector radii[4], int index, float rad) {
    421     sk_bzero(radii, sizeof(SkVector) * 4);
    422     radii[index].set(rad, rad);
    423 }
    424 
    425 static void test_skbug_3239(skiatest::Reporter* reporter) {
    426     const float min = SkBits2Float(0xcb7f16c8); /* -16717512.000000 */
    427     const float max = SkBits2Float(0x4b7f1c1d); /*  16718877.000000 */
    428     const float big = SkBits2Float(0x4b7f1bd7); /*  16718807.000000 */
    429 
    430     const float rad = 33436320;
    431 
    432     const SkRect rectx = SkRect::MakeLTRB(min, min, max, big);
    433     const SkRect recty = SkRect::MakeLTRB(min, min, big, max);
    434 
    435     for (auto dir : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) {
    436         for (int start = 0; start < 8; ++start) {
    437             SkVector radii[4];
    438             for (int i = 0; i < 4; ++i) {
    439                 set_radii(radii, i, rad);
    440                 path_contains_rrect_check(reporter, rectx, radii, dir, start);
    441                 path_contains_rrect_check(reporter, recty, radii, dir, start);
    442             }
    443         }
    444     }
    445 }
    446 
    447 static void test_mix(skiatest::Reporter* reporter) {
    448     for (auto dir : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) {
    449         for (int start = 0; start < 8; ++start) {
    450             // Test out mixed degenerate and non-degenerate geometry with Conics
    451             const SkVector radii[4] = { { 0, 0 }, { 0, 0 }, { 0, 0 }, { 100, 100 } };
    452             SkRect r = SkRect::MakeWH(100, 100);
    453             SkRRect rr;
    454             rr.setRectRadii(r, radii);
    455             path_contains_rrect_check(reporter, rr, dir, start);
    456         }
    457     }
    458 }
    459 
    460 DEF_TEST(RoundRectInPath, reporter) {
    461     test_tricky_radii(reporter);
    462     test_empty_crbug_458524(reporter);
    463     test_inset(reporter);
    464     test_round_rect_basic(reporter);
    465     test_round_rect_rects(reporter);
    466     test_round_rect_ovals(reporter);
    467     test_round_rect_general(reporter);
    468     test_undetected_paths(reporter);
    469     test_round_rect_iffy_parameters(reporter);
    470     test_skbug_3239(reporter);
    471     test_mix(reporter);
    472 }
    473 
    474 DEF_TEST(RRect_fragile, reporter) {
    475     SkRect rect = {
    476         SkBits2Float(0x1f800000),  // 0x003F0000 was the starter value that also fails
    477         SkBits2Float(0x1400001C),
    478         SkBits2Float(0x3F000004),
    479         SkBits2Float(0x3F000004),
    480     };
    481 
    482     SkPoint radii[] = {
    483         { SkBits2Float(0x00000001), SkBits2Float(0x00000001) },
    484         { SkBits2Float(0x00000020), SkBits2Float(0x00000001) },
    485         { SkBits2Float(0x00000000), SkBits2Float(0x00000000) },
    486         { SkBits2Float(0x3F000004), SkBits2Float(0x3F000004) },
    487     };
    488 
    489     SkRRect rr;
    490     // please don't assert
    491     if (false) {    // disable until we fix this
    492         SkDebugf("%g 0x%08X\n", rect.fLeft, SkFloat2Bits(rect.fLeft));
    493         rr.setRectRadii(rect, radii);
    494     }
    495 }
    496 
    497