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