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