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