Home | History | Annotate | Download | only in tests
      1 /*
      2  * Copyright 2011 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 "SkAAClip.h"
      9 #include "SkCanvas.h"
     10 #include "SkMask.h"
     11 #include "SkPath.h"
     12 #include "SkRandom.h"
     13 #include "SkRRect.h"
     14 #include "Test.h"
     15 
     16 static bool operator==(const SkMask& a, const SkMask& b) {
     17     if (a.fFormat != b.fFormat || a.fBounds != b.fBounds) {
     18         return false;
     19     }
     20     if (!a.fImage && !b.fImage) {
     21         return true;
     22     }
     23     if (!a.fImage || !b.fImage) {
     24         return false;
     25     }
     26 
     27     size_t wbytes = a.fBounds.width();
     28     switch (a.fFormat) {
     29         case SkMask::kBW_Format:
     30             wbytes = (wbytes + 7) >> 3;
     31             break;
     32         case SkMask::kA8_Format:
     33         case SkMask::k3D_Format:
     34             break;
     35         case SkMask::kLCD16_Format:
     36             wbytes <<= 1;
     37             break;
     38         case SkMask::kARGB32_Format:
     39             wbytes <<= 2;
     40             break;
     41         default:
     42             SkDEBUGFAIL("unknown mask format");
     43             return false;
     44     }
     45 
     46     const int h = a.fBounds.height();
     47     const char* aptr = (const char*)a.fImage;
     48     const char* bptr = (const char*)b.fImage;
     49     for (int y = 0; y < h; ++y) {
     50         if (memcmp(aptr, bptr, wbytes)) {
     51             return false;
     52         }
     53         aptr += wbytes;
     54         bptr += wbytes;
     55     }
     56     return true;
     57 }
     58 
     59 static void copyToMask(const SkRegion& rgn, SkMask* mask) {
     60     mask->fFormat = SkMask::kA8_Format;
     61 
     62     if (rgn.isEmpty()) {
     63         mask->fBounds.setEmpty();
     64         mask->fRowBytes = 0;
     65         mask->fImage = nullptr;
     66         return;
     67     }
     68 
     69     mask->fBounds = rgn.getBounds();
     70     mask->fRowBytes = mask->fBounds.width();
     71     mask->fImage = SkMask::AllocImage(mask->computeImageSize());
     72     sk_bzero(mask->fImage, mask->computeImageSize());
     73 
     74     SkImageInfo info = SkImageInfo::Make(mask->fBounds.width(),
     75                                          mask->fBounds.height(),
     76                                          kAlpha_8_SkColorType,
     77                                          kPremul_SkAlphaType);
     78     SkBitmap bitmap;
     79     bitmap.installPixels(info, mask->fImage, mask->fRowBytes);
     80 
     81     // canvas expects its coordinate system to always be 0,0 in the top/left
     82     // so we translate the rgn to match that before drawing into the mask.
     83     //
     84     SkRegion tmpRgn(rgn);
     85     tmpRgn.translate(-rgn.getBounds().fLeft, -rgn.getBounds().fTop);
     86 
     87     SkCanvas canvas(bitmap);
     88     canvas.clipRegion(tmpRgn);
     89     canvas.drawColor(SK_ColorBLACK);
     90 }
     91 
     92 static SkIRect rand_rect(SkRandom& rand, int n) {
     93     int x = rand.nextS() % n;
     94     int y = rand.nextS() % n;
     95     int w = rand.nextU() % n;
     96     int h = rand.nextU() % n;
     97     return SkIRect::MakeXYWH(x, y, w, h);
     98 }
     99 
    100 static void make_rand_rgn(SkRegion* rgn, SkRandom& rand) {
    101     int count = rand.nextU() % 20;
    102     for (int i = 0; i < count; ++i) {
    103         rgn->op(rand_rect(rand, 100), SkRegion::kXOR_Op);
    104     }
    105 }
    106 
    107 static bool operator==(const SkRegion& rgn, const SkAAClip& aaclip) {
    108     SkMask mask0, mask1;
    109 
    110     copyToMask(rgn, &mask0);
    111     aaclip.copyToMask(&mask1);
    112     bool eq = (mask0 == mask1);
    113 
    114     SkMask::FreeImage(mask0.fImage);
    115     SkMask::FreeImage(mask1.fImage);
    116     return eq;
    117 }
    118 
    119 static bool equalsAAClip(const SkRegion& rgn) {
    120     SkAAClip aaclip;
    121     aaclip.setRegion(rgn);
    122     return rgn == aaclip;
    123 }
    124 
    125 static void setRgnToPath(SkRegion* rgn, const SkPath& path) {
    126     SkIRect ir;
    127     path.getBounds().round(&ir);
    128     rgn->setPath(path, SkRegion(ir));
    129 }
    130 
    131 // aaclip.setRegion should create idential masks to the region
    132 static void test_rgn(skiatest::Reporter* reporter) {
    133     SkRandom rand;
    134     for (int i = 0; i < 1000; i++) {
    135         SkRegion rgn;
    136         make_rand_rgn(&rgn, rand);
    137         REPORTER_ASSERT(reporter, equalsAAClip(rgn));
    138     }
    139 
    140     {
    141         SkRegion rgn;
    142         SkPath path;
    143         path.addCircle(0, 0, SkIntToScalar(30));
    144         setRgnToPath(&rgn, path);
    145         REPORTER_ASSERT(reporter, equalsAAClip(rgn));
    146 
    147         path.reset();
    148         path.moveTo(0, 0);
    149         path.lineTo(SkIntToScalar(100), 0);
    150         path.lineTo(SkIntToScalar(100 - 20), SkIntToScalar(20));
    151         path.lineTo(SkIntToScalar(20), SkIntToScalar(20));
    152         setRgnToPath(&rgn, path);
    153         REPORTER_ASSERT(reporter, equalsAAClip(rgn));
    154     }
    155 }
    156 
    157 static const SkRegion::Op gRgnOps[] = {
    158     SkRegion::kDifference_Op,
    159     SkRegion::kIntersect_Op,
    160     SkRegion::kUnion_Op,
    161     SkRegion::kXOR_Op,
    162     SkRegion::kReverseDifference_Op,
    163     SkRegion::kReplace_Op
    164 };
    165 
    166 static const char* gRgnOpNames[] = {
    167     "DIFF", "INTERSECT", "UNION", "XOR", "REVERSE_DIFF", "REPLACE"
    168 };
    169 
    170 static void imoveTo(SkPath& path, int x, int y) {
    171     path.moveTo(SkIntToScalar(x), SkIntToScalar(y));
    172 }
    173 
    174 static void icubicTo(SkPath& path, int x0, int y0, int x1, int y1, int x2, int y2) {
    175     path.cubicTo(SkIntToScalar(x0), SkIntToScalar(y0),
    176                  SkIntToScalar(x1), SkIntToScalar(y1),
    177                  SkIntToScalar(x2), SkIntToScalar(y2));
    178 }
    179 
    180 static void test_path_bounds(skiatest::Reporter* reporter) {
    181     SkPath path;
    182     SkAAClip clip;
    183     const int height = 40;
    184     const SkScalar sheight = SkIntToScalar(height);
    185 
    186     path.addOval(SkRect::MakeWH(sheight, sheight));
    187     REPORTER_ASSERT(reporter, sheight == path.getBounds().height());
    188     clip.setPath(path, nullptr, true);
    189     REPORTER_ASSERT(reporter, height == clip.getBounds().height());
    190 
    191     // this is the trimmed height of this cubic (with aa). The critical thing
    192     // for this test is that it is less than height, which represents just
    193     // the bounds of the path's control-points.
    194     //
    195     // This used to fail until we tracked the MinY in the BuilderBlitter.
    196     //
    197     const int teardrop_height = 12;
    198     path.reset();
    199     imoveTo(path, 0, 20);
    200     icubicTo(path, 40, 40, 40, 0, 0, 20);
    201     REPORTER_ASSERT(reporter, sheight == path.getBounds().height());
    202     clip.setPath(path, nullptr, true);
    203     REPORTER_ASSERT(reporter, teardrop_height == clip.getBounds().height());
    204 }
    205 
    206 static void test_empty(skiatest::Reporter* reporter) {
    207     SkAAClip clip0, clip1;
    208 
    209     REPORTER_ASSERT(reporter, clip0.isEmpty());
    210     REPORTER_ASSERT(reporter, clip0.getBounds().isEmpty());
    211     REPORTER_ASSERT(reporter, clip1 == clip0);
    212 
    213     clip0.translate(10, 10);    // should have no effect on empty
    214     REPORTER_ASSERT(reporter, clip0.isEmpty());
    215     REPORTER_ASSERT(reporter, clip0.getBounds().isEmpty());
    216     REPORTER_ASSERT(reporter, clip1 == clip0);
    217 
    218     SkIRect r = { 10, 10, 40, 50 };
    219     clip0.setRect(r);
    220     REPORTER_ASSERT(reporter, !clip0.isEmpty());
    221     REPORTER_ASSERT(reporter, !clip0.getBounds().isEmpty());
    222     REPORTER_ASSERT(reporter, clip0 != clip1);
    223     REPORTER_ASSERT(reporter, clip0.getBounds() == r);
    224 
    225     clip0.setEmpty();
    226     REPORTER_ASSERT(reporter, clip0.isEmpty());
    227     REPORTER_ASSERT(reporter, clip0.getBounds().isEmpty());
    228     REPORTER_ASSERT(reporter, clip1 == clip0);
    229 
    230     SkMask mask;
    231     clip0.copyToMask(&mask);
    232     REPORTER_ASSERT(reporter, nullptr == mask.fImage);
    233     REPORTER_ASSERT(reporter, mask.fBounds.isEmpty());
    234 }
    235 
    236 static void rand_irect(SkIRect* r, int N, SkRandom& rand) {
    237     r->setXYWH(0, 0, rand.nextU() % N, rand.nextU() % N);
    238     int dx = rand.nextU() % (2*N);
    239     int dy = rand.nextU() % (2*N);
    240     // use int dx,dy to make the subtract be signed
    241     r->offset(N - dx, N - dy);
    242 }
    243 
    244 static void test_irect(skiatest::Reporter* reporter) {
    245     SkRandom rand;
    246 
    247     for (int i = 0; i < 10000; i++) {
    248         SkAAClip clip0, clip1;
    249         SkRegion rgn0, rgn1;
    250         SkIRect r0, r1;
    251 
    252         rand_irect(&r0, 10, rand);
    253         rand_irect(&r1, 10, rand);
    254         clip0.setRect(r0);
    255         clip1.setRect(r1);
    256         rgn0.setRect(r0);
    257         rgn1.setRect(r1);
    258         for (size_t j = 0; j < SK_ARRAY_COUNT(gRgnOps); ++j) {
    259             SkRegion::Op op = gRgnOps[j];
    260             SkAAClip clip2;
    261             SkRegion rgn2;
    262             bool nonEmptyAA = clip2.op(clip0, clip1, op);
    263             bool nonEmptyBW = rgn2.op(rgn0, rgn1, op);
    264             if (nonEmptyAA != nonEmptyBW || clip2.getBounds() != rgn2.getBounds()) {
    265                 ERRORF(reporter, "%s %s "
    266                        "[%d %d %d %d] %s [%d %d %d %d] = BW:[%d %d %d %d] AA:[%d %d %d %d]\n",
    267                        nonEmptyAA == nonEmptyBW ? "true" : "false",
    268                        clip2.getBounds() == rgn2.getBounds() ? "true" : "false",
    269                        r0.fLeft, r0.fTop, r0.right(), r0.bottom(),
    270                        gRgnOpNames[j],
    271                        r1.fLeft, r1.fTop, r1.right(), r1.bottom(),
    272                        rgn2.getBounds().fLeft, rgn2.getBounds().fTop,
    273                        rgn2.getBounds().right(), rgn2.getBounds().bottom(),
    274                        clip2.getBounds().fLeft, clip2.getBounds().fTop,
    275                        clip2.getBounds().right(), clip2.getBounds().bottom());
    276             }
    277 
    278             SkMask maskBW, maskAA;
    279             copyToMask(rgn2, &maskBW);
    280             clip2.copyToMask(&maskAA);
    281             SkAutoMaskFreeImage freeBW(maskBW.fImage);
    282             SkAutoMaskFreeImage freeAA(maskAA.fImage);
    283             REPORTER_ASSERT(reporter, maskBW == maskAA);
    284         }
    285     }
    286 }
    287 
    288 static void test_path_with_hole(skiatest::Reporter* reporter) {
    289     static const uint8_t gExpectedImage[] = {
    290         0xFF, 0xFF, 0xFF, 0xFF,
    291         0xFF, 0xFF, 0xFF, 0xFF,
    292         0x00, 0x00, 0x00, 0x00,
    293         0x00, 0x00, 0x00, 0x00,
    294         0xFF, 0xFF, 0xFF, 0xFF,
    295         0xFF, 0xFF, 0xFF, 0xFF,
    296     };
    297     SkMask expected;
    298     expected.fBounds.set(0, 0, 4, 6);
    299     expected.fRowBytes = 4;
    300     expected.fFormat = SkMask::kA8_Format;
    301     expected.fImage = (uint8_t*)gExpectedImage;
    302 
    303     SkPath path;
    304     path.addRect(SkRect::MakeXYWH(0, 0,
    305                                   SkIntToScalar(4), SkIntToScalar(2)));
    306     path.addRect(SkRect::MakeXYWH(0, SkIntToScalar(4),
    307                                   SkIntToScalar(4), SkIntToScalar(2)));
    308 
    309     for (int i = 0; i < 2; ++i) {
    310         SkAAClip clip;
    311         clip.setPath(path, nullptr, 1 == i);
    312 
    313         SkMask mask;
    314         clip.copyToMask(&mask);
    315         SkAutoMaskFreeImage freeM(mask.fImage);
    316 
    317         REPORTER_ASSERT(reporter, expected == mask);
    318     }
    319 }
    320 
    321 static void test_really_a_rect(skiatest::Reporter* reporter) {
    322     SkRRect rrect;
    323     rrect.setRectXY(SkRect::MakeWH(100, 100), 5, 5);
    324 
    325     SkPath path;
    326     path.addRRect(rrect);
    327 
    328     SkAAClip clip;
    329     clip.setPath(path);
    330 
    331     REPORTER_ASSERT(reporter, clip.getBounds() == SkIRect::MakeWH(100, 100));
    332     REPORTER_ASSERT(reporter, !clip.isRect());
    333 
    334     // This rect should intersect the clip, but slice-out all of the "soft" parts,
    335     // leaving just a rect.
    336     const SkIRect ir = SkIRect::MakeLTRB(10, -10, 50, 90);
    337 
    338     clip.op(ir, SkRegion::kIntersect_Op);
    339 
    340     REPORTER_ASSERT(reporter, clip.getBounds() == SkIRect::MakeLTRB(10, 0, 50, 90));
    341     // the clip recognized that that it is just a rect!
    342     REPORTER_ASSERT(reporter, clip.isRect());
    343 }
    344 
    345 #include "SkRasterClip.h"
    346 
    347 static void copyToMask(const SkRasterClip& rc, SkMask* mask) {
    348     if (rc.isAA()) {
    349         rc.aaRgn().copyToMask(mask);
    350     } else {
    351         copyToMask(rc.bwRgn(), mask);
    352     }
    353 }
    354 
    355 static bool operator==(const SkRasterClip& a, const SkRasterClip& b) {
    356     if (a.isEmpty()) {
    357         return b.isEmpty();
    358     }
    359     if (b.isEmpty()) {
    360         return false;
    361     }
    362 
    363     SkMask ma, mb;
    364     copyToMask(a, &ma);
    365     copyToMask(b, &mb);
    366     SkAutoMaskFreeImage aCleanUp(ma.fImage);
    367     SkAutoMaskFreeImage bCleanUp(mb.fImage);
    368 
    369     return ma == mb;
    370 }
    371 
    372 static void did_dx_affect(skiatest::Reporter* reporter, const SkScalar dx[],
    373                           size_t count, bool changed) {
    374     const SkIRect baseBounds = SkIRect::MakeXYWH(0, 0, 10, 10);
    375     SkIRect ir = { 0, 0, 10, 10 };
    376 
    377     for (size_t i = 0; i < count; ++i) {
    378         SkRect r;
    379         r.set(ir);
    380 
    381         SkRasterClip rc0(ir);
    382         SkRasterClip rc1(ir);
    383         SkRasterClip rc2(ir);
    384 
    385         rc0.op(r, baseBounds, SkRegion::kIntersect_Op, false);
    386         r.offset(dx[i], 0);
    387         rc1.op(r, baseBounds, SkRegion::kIntersect_Op, true);
    388         r.offset(-2*dx[i], 0);
    389         rc2.op(r, baseBounds, SkRegion::kIntersect_Op, true);
    390 
    391         REPORTER_ASSERT(reporter, changed != (rc0 == rc1));
    392         REPORTER_ASSERT(reporter, changed != (rc0 == rc2));
    393     }
    394 }
    395 
    396 static void test_nearly_integral(skiatest::Reporter* reporter) {
    397     // All of these should generate equivalent rasterclips
    398 
    399     static const SkScalar gSafeX[] = {
    400         0, SK_Scalar1/1000, SK_Scalar1/100, SK_Scalar1/10,
    401     };
    402     did_dx_affect(reporter, gSafeX, SK_ARRAY_COUNT(gSafeX), false);
    403 
    404     static const SkScalar gUnsafeX[] = {
    405         SK_Scalar1/4, SK_Scalar1/3,
    406     };
    407     did_dx_affect(reporter, gUnsafeX, SK_ARRAY_COUNT(gUnsafeX), true);
    408 }
    409 
    410 static void test_regressions() {
    411     // these should not assert in the debug build
    412     // bug was introduced in rev. 3209
    413     {
    414         SkAAClip clip;
    415         SkRect r;
    416         r.fLeft = 129.892181f;
    417         r.fTop = 10.3999996f;
    418         r.fRight = 130.892181f;
    419         r.fBottom = 20.3999996f;
    420         clip.setRect(r, true);
    421     }
    422 }
    423 
    424 // Building aaclip meant aa-scan-convert a path into a huge clip.
    425 // the old algorithm sized the supersampler to the size of the clip, which overflowed
    426 // its internal 16bit coordinates. The fix was to intersect the clip+path_bounds before
    427 // sizing the supersampler.
    428 //
    429 // Before the fix, the following code would assert in debug builds.
    430 //
    431 static void test_crbug_422693(skiatest::Reporter* reporter) {
    432     SkRasterClip rc(SkIRect::MakeLTRB(-25000, -25000, 25000, 25000));
    433     SkPath path;
    434     path.addCircle(50, 50, 50);
    435     rc.op(path, rc.getBounds(), SkRegion::kIntersect_Op, true);
    436 }
    437 
    438 DEF_TEST(AAClip, reporter) {
    439     test_empty(reporter);
    440     test_path_bounds(reporter);
    441     test_irect(reporter);
    442     test_rgn(reporter);
    443     test_path_with_hole(reporter);
    444     test_regressions();
    445     test_nearly_integral(reporter);
    446     test_really_a_rect(reporter);
    447     test_crbug_422693(reporter);
    448 }
    449