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