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 SkASSERT(!"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 REPORTER_ASSERT(reporter, maskBW == maskAA); 280 } 281 } 282 } 283 284 static void test_path_with_hole(skiatest::Reporter* reporter) { 285 static const uint8_t gExpectedImage[] = { 286 0xFF, 0xFF, 0xFF, 0xFF, 287 0xFF, 0xFF, 0xFF, 0xFF, 288 0x00, 0x00, 0x00, 0x00, 289 0x00, 0x00, 0x00, 0x00, 290 0xFF, 0xFF, 0xFF, 0xFF, 291 0xFF, 0xFF, 0xFF, 0xFF, 292 }; 293 SkMask expected; 294 expected.fBounds.set(0, 0, 4, 6); 295 expected.fRowBytes = 4; 296 expected.fFormat = SkMask::kA8_Format; 297 expected.fImage = (uint8_t*)gExpectedImage; 298 299 SkPath path; 300 path.addRect(SkRect::MakeXYWH(0, 0, 301 SkIntToScalar(4), SkIntToScalar(2))); 302 path.addRect(SkRect::MakeXYWH(0, SkIntToScalar(4), 303 SkIntToScalar(4), SkIntToScalar(2))); 304 305 for (int i = 0; i < 2; ++i) { 306 SkAAClip clip; 307 clip.setPath(path, NULL, 1 == i); 308 309 SkMask mask; 310 clip.copyToMask(&mask); 311 312 REPORTER_ASSERT(reporter, expected == mask); 313 } 314 } 315 316 static void test_regressions(skiatest::Reporter* reporter) { 317 // these should not assert in the debug build 318 // bug was introduced in rev. 3209 319 { 320 SkAAClip clip; 321 SkRect r; 322 r.fLeft = SkFloatToScalar(129.892181); 323 r.fTop = SkFloatToScalar(10.3999996); 324 r.fRight = SkFloatToScalar(130.892181); 325 r.fBottom = SkFloatToScalar(20.3999996); 326 clip.setRect(r, true); 327 } 328 } 329 330 static void TestAAClip(skiatest::Reporter* reporter) { 331 test_empty(reporter); 332 test_path_bounds(reporter); 333 test_irect(reporter); 334 test_rgn(reporter); 335 test_path_with_hole(reporter); 336 test_regressions(reporter); 337 } 338 339 #include "TestClassDef.h" 340 DEFINE_TESTCLASS("AAClip", AAClipTestClass, TestAAClip) 341