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 "Test.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     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, NULL, 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, NULL, 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     mask.fImage = NULL;
    232     clip0.copyToMask(&mask);
    233     REPORTER_ASSERT(reporter, NULL == 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                 SkDebugf("[%d %d %d %d] %s [%d %d %d %d] = BW:[%d %d %d %d] AA:[%d %d %d %d]\n",
    267                          r0.fLeft, r0.fTop, r0.right(), r0.bottom(),
    268                          gRgnOpNames[j],
    269                          r1.fLeft, r1.fTop, r1.right(), r1.bottom(),
    270                          rgn2.getBounds().fLeft, rgn2.getBounds().fTop,
    271                          rgn2.getBounds().right(), rgn2.getBounds().bottom(),
    272                          clip2.getBounds().fLeft, clip2.getBounds().fTop,
    273                          clip2.getBounds().right(), clip2.getBounds().bottom());
    274             }
    275             REPORTER_ASSERT(reporter, nonEmptyAA == nonEmptyBW);
    276             REPORTER_ASSERT(reporter, clip2.getBounds() == rgn2.getBounds());
    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, NULL, 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 #include "SkRasterClip.h"
    322 
    323 static void copyToMask(const SkRasterClip& rc, SkMask* mask) {
    324     if (rc.isAA()) {
    325         rc.aaRgn().copyToMask(mask);
    326     } else {
    327         copyToMask(rc.bwRgn(), mask);
    328     }
    329 }
    330 
    331 static bool operator==(const SkRasterClip& a, const SkRasterClip& b) {
    332     if (a.isEmpty()) {
    333         return b.isEmpty();
    334     }
    335     if (b.isEmpty()) {
    336         return false;
    337     }
    338 
    339     SkMask ma, mb;
    340     copyToMask(a, &ma);
    341     copyToMask(b, &mb);
    342     SkAutoMaskFreeImage aCleanUp(ma.fImage);
    343     SkAutoMaskFreeImage bCleanUp(mb.fImage);
    344 
    345     return ma == mb;
    346 }
    347 
    348 static void did_dx_affect(skiatest::Reporter* reporter, const SkScalar dx[],
    349                           size_t count, bool changed) {
    350     SkIRect ir = { 0, 0, 10, 10 };
    351 
    352     for (size_t i = 0; i < count; ++i) {
    353         SkRect r;
    354         r.set(ir);
    355 
    356         SkRasterClip rc0(ir);
    357         SkRasterClip rc1(ir);
    358         SkRasterClip rc2(ir);
    359 
    360         rc0.op(r, SkRegion::kIntersect_Op, false);
    361         r.offset(dx[i], 0);
    362         rc1.op(r, SkRegion::kIntersect_Op, true);
    363         r.offset(-2*dx[i], 0);
    364         rc2.op(r, SkRegion::kIntersect_Op, true);
    365 
    366         REPORTER_ASSERT(reporter, changed != (rc0 == rc1));
    367         REPORTER_ASSERT(reporter, changed != (rc0 == rc2));
    368     }
    369 }
    370 
    371 static void test_nearly_integral(skiatest::Reporter* reporter) {
    372     // All of these should generate equivalent rasterclips
    373 
    374     static const SkScalar gSafeX[] = {
    375         0, SK_Scalar1/1000, SK_Scalar1/100, SK_Scalar1/10,
    376     };
    377     did_dx_affect(reporter, gSafeX, SK_ARRAY_COUNT(gSafeX), false);
    378 
    379     static const SkScalar gUnsafeX[] = {
    380         SK_Scalar1/4, SK_Scalar1/3,
    381     };
    382     did_dx_affect(reporter, gUnsafeX, SK_ARRAY_COUNT(gUnsafeX), true);
    383 }
    384 
    385 static void test_regressions() {
    386     // these should not assert in the debug build
    387     // bug was introduced in rev. 3209
    388     {
    389         SkAAClip clip;
    390         SkRect r;
    391         r.fLeft = 129.892181f;
    392         r.fTop = 10.3999996f;
    393         r.fRight = 130.892181f;
    394         r.fBottom = 20.3999996f;
    395         clip.setRect(r, true);
    396     }
    397 }
    398 
    399 DEF_TEST(AAClip, reporter) {
    400     test_empty(reporter);
    401     test_path_bounds(reporter);
    402     test_irect(reporter);
    403     test_rgn(reporter);
    404     test_path_with_hole(reporter);
    405     test_regressions();
    406     test_nearly_integral(reporter);
    407 }
    408