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