blob: 438dab4da88032a0940b90c834f2531f14d81f7a [file] [log] [blame]
reed@google.com209c4152011-10-26 15:03:48 +00001/*
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
reed@google.com209c4152011-10-26 15:03:48 +00008#include "SkAAClip.h"
mike@reedtribe.org95b85bd2011-11-23 03:12:58 +00009#include "SkCanvas.h"
10#include "SkMask.h"
reed@google.com209c4152011-10-26 15:03:48 +000011#include "SkPath.h"
reed@google.comd8676d22011-10-26 18:01:25 +000012#include "SkRandom.h"
reed1e7f5e72016-04-27 07:49:17 -070013#include "SkRasterClip.h"
bungemand3ebb482015-08-05 13:57:49 -070014#include "SkRRect.h"
tfarina@chromium.org8f6884a2014-01-24 20:56:26 +000015#include "Test.h"
reed@google.comd8676d22011-10-26 18:01:25 +000016
mike@reedtribe.org95b85bd2011-11-23 03:12:58 +000017static bool operator==(const SkMask& a, const SkMask& b) {
18 if (a.fFormat != b.fFormat || a.fBounds != b.fBounds) {
19 return false;
20 }
21 if (!a.fImage && !b.fImage) {
22 return true;
23 }
24 if (!a.fImage || !b.fImage) {
25 return false;
26 }
27
28 size_t wbytes = a.fBounds.width();
29 switch (a.fFormat) {
30 case SkMask::kBW_Format:
31 wbytes = (wbytes + 7) >> 3;
32 break;
33 case SkMask::kA8_Format:
34 case SkMask::k3D_Format:
35 break;
36 case SkMask::kLCD16_Format:
37 wbytes <<= 1;
38 break;
mike@reedtribe.org95b85bd2011-11-23 03:12:58 +000039 case SkMask::kARGB32_Format:
40 wbytes <<= 2;
41 break;
42 default:
mtklein@google.com330313a2013-08-22 15:37:26 +000043 SkDEBUGFAIL("unknown mask format");
mike@reedtribe.org95b85bd2011-11-23 03:12:58 +000044 return false;
45 }
46
47 const int h = a.fBounds.height();
48 const char* aptr = (const char*)a.fImage;
49 const char* bptr = (const char*)b.fImage;
50 for (int y = 0; y < h; ++y) {
51 if (memcmp(aptr, bptr, wbytes)) {
52 return false;
53 }
54 aptr += wbytes;
55 bptr += wbytes;
56 }
57 return true;
58}
59
60static void copyToMask(const SkRegion& rgn, SkMask* mask) {
reed@google.coma052aca2011-11-23 19:43:46 +000061 mask->fFormat = SkMask::kA8_Format;
62
mike@reedtribe.org95b85bd2011-11-23 03:12:58 +000063 if (rgn.isEmpty()) {
mike@reedtribe.org95b85bd2011-11-23 03:12:58 +000064 mask->fBounds.setEmpty();
65 mask->fRowBytes = 0;
halcanary96fcdcc2015-08-27 07:41:13 -070066 mask->fImage = nullptr;
mike@reedtribe.org95b85bd2011-11-23 03:12:58 +000067 return;
68 }
69
70 mask->fBounds = rgn.getBounds();
71 mask->fRowBytes = mask->fBounds.width();
mike@reedtribe.org95b85bd2011-11-23 03:12:58 +000072 mask->fImage = SkMask::AllocImage(mask->computeImageSize());
73 sk_bzero(mask->fImage, mask->computeImageSize());
74
commit-bot@chromium.orgfa9e5fa2014-02-13 22:00:04 +000075 SkImageInfo info = SkImageInfo::Make(mask->fBounds.width(),
76 mask->fBounds.height(),
77 kAlpha_8_SkColorType,
78 kPremul_SkAlphaType);
mike@reedtribe.org95b85bd2011-11-23 03:12:58 +000079 SkBitmap bitmap;
commit-bot@chromium.org00f8d6c2014-05-29 15:57:20 +000080 bitmap.installPixels(info, mask->fImage, mask->fRowBytes);
mike@reedtribe.org95b85bd2011-11-23 03:12:58 +000081
82 // canvas expects its coordinate system to always be 0,0 in the top/left
83 // so we translate the rgn to match that before drawing into the mask.
84 //
85 SkRegion tmpRgn(rgn);
86 tmpRgn.translate(-rgn.getBounds().fLeft, -rgn.getBounds().fTop);
87
88 SkCanvas canvas(bitmap);
89 canvas.clipRegion(tmpRgn);
90 canvas.drawColor(SK_ColorBLACK);
91}
92
commit-bot@chromium.orge0e7cfe2013-09-09 20:09:12 +000093static SkIRect rand_rect(SkRandom& rand, int n) {
mike@reedtribe.org95b85bd2011-11-23 03:12:58 +000094 int x = rand.nextS() % n;
95 int y = rand.nextS() % n;
96 int w = rand.nextU() % n;
97 int h = rand.nextU() % n;
98 return SkIRect::MakeXYWH(x, y, w, h);
99}
100
commit-bot@chromium.orge0e7cfe2013-09-09 20:09:12 +0000101static void make_rand_rgn(SkRegion* rgn, SkRandom& rand) {
mike@reedtribe.org95b85bd2011-11-23 03:12:58 +0000102 int count = rand.nextU() % 20;
103 for (int i = 0; i < count; ++i) {
104 rgn->op(rand_rect(rand, 100), SkRegion::kXOR_Op);
105 }
106}
107
reed@google.coma052aca2011-11-23 19:43:46 +0000108static bool operator==(const SkRegion& rgn, const SkAAClip& aaclip) {
109 SkMask mask0, mask1;
110
111 copyToMask(rgn, &mask0);
112 aaclip.copyToMask(&mask1);
113 bool eq = (mask0 == mask1);
114
115 SkMask::FreeImage(mask0.fImage);
116 SkMask::FreeImage(mask1.fImage);
117 return eq;
118}
119
120static bool equalsAAClip(const SkRegion& rgn) {
121 SkAAClip aaclip;
122 aaclip.setRegion(rgn);
123 return rgn == aaclip;
124}
125
126static void setRgnToPath(SkRegion* rgn, const SkPath& path) {
127 SkIRect ir;
128 path.getBounds().round(&ir);
129 rgn->setPath(path, SkRegion(ir));
130}
131
mike@reedtribe.org95b85bd2011-11-23 03:12:58 +0000132// aaclip.setRegion should create idential masks to the region
133static void test_rgn(skiatest::Reporter* reporter) {
commit-bot@chromium.orge0e7cfe2013-09-09 20:09:12 +0000134 SkRandom rand;
mike@reedtribe.org95b85bd2011-11-23 03:12:58 +0000135 for (int i = 0; i < 1000; i++) {
136 SkRegion rgn;
137 make_rand_rgn(&rgn, rand);
reed@google.coma052aca2011-11-23 19:43:46 +0000138 REPORTER_ASSERT(reporter, equalsAAClip(rgn));
139 }
140
141 {
142 SkRegion rgn;
143 SkPath path;
144 path.addCircle(0, 0, SkIntToScalar(30));
145 setRgnToPath(&rgn, path);
146 REPORTER_ASSERT(reporter, equalsAAClip(rgn));
147
148 path.reset();
149 path.moveTo(0, 0);
150 path.lineTo(SkIntToScalar(100), 0);
151 path.lineTo(SkIntToScalar(100 - 20), SkIntToScalar(20));
152 path.lineTo(SkIntToScalar(20), SkIntToScalar(20));
153 setRgnToPath(&rgn, path);
154 REPORTER_ASSERT(reporter, equalsAAClip(rgn));
mike@reedtribe.org95b85bd2011-11-23 03:12:58 +0000155 }
156}
157
reed@google.comd8676d22011-10-26 18:01:25 +0000158static const SkRegion::Op gRgnOps[] = {
reed@google.comc9041912011-10-27 16:58:46 +0000159 SkRegion::kDifference_Op,
reed@google.comd8676d22011-10-26 18:01:25 +0000160 SkRegion::kIntersect_Op,
161 SkRegion::kUnion_Op,
162 SkRegion::kXOR_Op,
reed@google.comc9041912011-10-27 16:58:46 +0000163 SkRegion::kReverseDifference_Op,
reed@google.comd8676d22011-10-26 18:01:25 +0000164 SkRegion::kReplace_Op
165};
166
167static const char* gRgnOpNames[] = {
reed@google.comc9041912011-10-27 16:58:46 +0000168 "DIFF", "INTERSECT", "UNION", "XOR", "REVERSE_DIFF", "REPLACE"
reed@google.comd8676d22011-10-26 18:01:25 +0000169};
reed@google.com209c4152011-10-26 15:03:48 +0000170
reed@google.com12e15252011-10-26 15:19:36 +0000171static void imoveTo(SkPath& path, int x, int y) {
172 path.moveTo(SkIntToScalar(x), SkIntToScalar(y));
173}
174
175static void icubicTo(SkPath& path, int x0, int y0, int x1, int y1, int x2, int y2) {
176 path.cubicTo(SkIntToScalar(x0), SkIntToScalar(y0),
177 SkIntToScalar(x1), SkIntToScalar(y1),
178 SkIntToScalar(x2), SkIntToScalar(y2));
179}
180
reed@google.comd8676d22011-10-26 18:01:25 +0000181static void test_path_bounds(skiatest::Reporter* reporter) {
reed@google.com209c4152011-10-26 15:03:48 +0000182 SkPath path;
183 SkAAClip clip;
184 const int height = 40;
185 const SkScalar sheight = SkIntToScalar(height);
186
187 path.addOval(SkRect::MakeWH(sheight, sheight));
188 REPORTER_ASSERT(reporter, sheight == path.getBounds().height());
halcanary96fcdcc2015-08-27 07:41:13 -0700189 clip.setPath(path, nullptr, true);
reed@google.com209c4152011-10-26 15:03:48 +0000190 REPORTER_ASSERT(reporter, height == clip.getBounds().height());
191
192 // this is the trimmed height of this cubic (with aa). The critical thing
193 // for this test is that it is less than height, which represents just
194 // the bounds of the path's control-points.
195 //
196 // This used to fail until we tracked the MinY in the BuilderBlitter.
197 //
198 const int teardrop_height = 12;
199 path.reset();
reed@google.com12e15252011-10-26 15:19:36 +0000200 imoveTo(path, 0, 20);
201 icubicTo(path, 40, 40, 40, 0, 0, 20);
reed@google.com209c4152011-10-26 15:03:48 +0000202 REPORTER_ASSERT(reporter, sheight == path.getBounds().height());
halcanary96fcdcc2015-08-27 07:41:13 -0700203 clip.setPath(path, nullptr, true);
reed@google.com209c4152011-10-26 15:03:48 +0000204 REPORTER_ASSERT(reporter, teardrop_height == clip.getBounds().height());
205}
206
reed@google.comd8676d22011-10-26 18:01:25 +0000207static void test_empty(skiatest::Reporter* reporter) {
208 SkAAClip clip0, clip1;
209
210 REPORTER_ASSERT(reporter, clip0.isEmpty());
211 REPORTER_ASSERT(reporter, clip0.getBounds().isEmpty());
212 REPORTER_ASSERT(reporter, clip1 == clip0);
213
214 clip0.translate(10, 10); // should have no effect on empty
215 REPORTER_ASSERT(reporter, clip0.isEmpty());
216 REPORTER_ASSERT(reporter, clip0.getBounds().isEmpty());
217 REPORTER_ASSERT(reporter, clip1 == clip0);
218
219 SkIRect r = { 10, 10, 40, 50 };
220 clip0.setRect(r);
221 REPORTER_ASSERT(reporter, !clip0.isEmpty());
222 REPORTER_ASSERT(reporter, !clip0.getBounds().isEmpty());
223 REPORTER_ASSERT(reporter, clip0 != clip1);
224 REPORTER_ASSERT(reporter, clip0.getBounds() == r);
225
226 clip0.setEmpty();
227 REPORTER_ASSERT(reporter, clip0.isEmpty());
228 REPORTER_ASSERT(reporter, clip0.getBounds().isEmpty());
229 REPORTER_ASSERT(reporter, clip1 == clip0);
230
231 SkMask mask;
reed@google.comd8676d22011-10-26 18:01:25 +0000232 clip0.copyToMask(&mask);
halcanary96fcdcc2015-08-27 07:41:13 -0700233 REPORTER_ASSERT(reporter, nullptr == mask.fImage);
reed@google.comd8676d22011-10-26 18:01:25 +0000234 REPORTER_ASSERT(reporter, mask.fBounds.isEmpty());
235}
236
commit-bot@chromium.orge0e7cfe2013-09-09 20:09:12 +0000237static void rand_irect(SkIRect* r, int N, SkRandom& rand) {
reed@google.comd8676d22011-10-26 18:01:25 +0000238 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
245static void test_irect(skiatest::Reporter* reporter) {
commit-bot@chromium.orge0e7cfe2013-09-09 20:09:12 +0000246 SkRandom rand;
reed@google.comd8676d22011-10-26 18:01:25 +0000247
reed@google.comc9041912011-10-27 16:58:46 +0000248 for (int i = 0; i < 10000; i++) {
reed@google.comd8676d22011-10-26 18:01:25 +0000249 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()) {
halcanary7d571242016-02-24 17:59:16 -0800266 ERRORF(reporter, "%s %s "
267 "[%d %d %d %d] %s [%d %d %d %d] = BW:[%d %d %d %d] AA:[%d %d %d %d]\n",
268 nonEmptyAA == nonEmptyBW ? "true" : "false",
269 clip2.getBounds() == rgn2.getBounds() ? "true" : "false",
270 r0.fLeft, r0.fTop, r0.right(), r0.bottom(),
271 gRgnOpNames[j],
272 r1.fLeft, r1.fTop, r1.right(), r1.bottom(),
273 rgn2.getBounds().fLeft, rgn2.getBounds().fTop,
274 rgn2.getBounds().right(), rgn2.getBounds().bottom(),
275 clip2.getBounds().fLeft, clip2.getBounds().fTop,
276 clip2.getBounds().right(), clip2.getBounds().bottom());
reed@google.comd8676d22011-10-26 18:01:25 +0000277 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000278
reed@google.com80cdb9a2012-02-16 18:56:17 +0000279 SkMask maskBW, maskAA;
280 copyToMask(rgn2, &maskBW);
281 clip2.copyToMask(&maskAA);
tomhudson@google.com0435f162012-03-15 18:16:39 +0000282 SkAutoMaskFreeImage freeBW(maskBW.fImage);
283 SkAutoMaskFreeImage freeAA(maskAA.fImage);
reed@google.com80cdb9a2012-02-16 18:56:17 +0000284 REPORTER_ASSERT(reporter, maskBW == maskAA);
reed@google.comd8676d22011-10-26 18:01:25 +0000285 }
286 }
287}
288
reed@google.com80cdb9a2012-02-16 18:56:17 +0000289static void test_path_with_hole(skiatest::Reporter* reporter) {
290 static const uint8_t gExpectedImage[] = {
291 0xFF, 0xFF, 0xFF, 0xFF,
292 0xFF, 0xFF, 0xFF, 0xFF,
293 0x00, 0x00, 0x00, 0x00,
294 0x00, 0x00, 0x00, 0x00,
295 0xFF, 0xFF, 0xFF, 0xFF,
296 0xFF, 0xFF, 0xFF, 0xFF,
297 };
298 SkMask expected;
299 expected.fBounds.set(0, 0, 4, 6);
300 expected.fRowBytes = 4;
301 expected.fFormat = SkMask::kA8_Format;
302 expected.fImage = (uint8_t*)gExpectedImage;
303
304 SkPath path;
305 path.addRect(SkRect::MakeXYWH(0, 0,
306 SkIntToScalar(4), SkIntToScalar(2)));
307 path.addRect(SkRect::MakeXYWH(0, SkIntToScalar(4),
308 SkIntToScalar(4), SkIntToScalar(2)));
309
310 for (int i = 0; i < 2; ++i) {
311 SkAAClip clip;
halcanary96fcdcc2015-08-27 07:41:13 -0700312 clip.setPath(path, nullptr, 1 == i);
rmistry@google.comd6176b02012-08-23 18:14:13 +0000313
reed@google.com80cdb9a2012-02-16 18:56:17 +0000314 SkMask mask;
315 clip.copyToMask(&mask);
tomhudson@google.com0435f162012-03-15 18:16:39 +0000316 SkAutoMaskFreeImage freeM(mask.fImage);
317
reed@google.com80cdb9a2012-02-16 18:56:17 +0000318 REPORTER_ASSERT(reporter, expected == mask);
319 }
320}
321
reed202ab2a2014-08-07 11:48:10 -0700322static void test_really_a_rect(skiatest::Reporter* reporter) {
323 SkRRect rrect;
324 rrect.setRectXY(SkRect::MakeWH(100, 100), 5, 5);
325
326 SkPath path;
327 path.addRRect(rrect);
328
329 SkAAClip clip;
330 clip.setPath(path);
331
332 REPORTER_ASSERT(reporter, clip.getBounds() == SkIRect::MakeWH(100, 100));
333 REPORTER_ASSERT(reporter, !clip.isRect());
334
335 // This rect should intersect the clip, but slice-out all of the "soft" parts,
336 // leaving just a rect.
337 const SkIRect ir = SkIRect::MakeLTRB(10, -10, 50, 90);
halcanary9d524f22016-03-29 09:03:52 -0700338
reed202ab2a2014-08-07 11:48:10 -0700339 clip.op(ir, SkRegion::kIntersect_Op);
340
341 REPORTER_ASSERT(reporter, clip.getBounds() == SkIRect::MakeLTRB(10, 0, 50, 90));
342 // the clip recognized that that it is just a rect!
343 REPORTER_ASSERT(reporter, clip.isRect());
344}
345
reed@google.com18c464b2012-05-11 20:57:25 +0000346static void did_dx_affect(skiatest::Reporter* reporter, const SkScalar dx[],
347 size_t count, bool changed) {
senorblancoafc7cce2016-02-02 18:44:15 -0800348 const SkIRect baseBounds = SkIRect::MakeXYWH(0, 0, 10, 10);
reed@google.com18c464b2012-05-11 20:57:25 +0000349 SkIRect ir = { 0, 0, 10, 10 };
350
351 for (size_t i = 0; i < count; ++i) {
352 SkRect r;
353 r.set(ir);
rmistry@google.comd6176b02012-08-23 18:14:13 +0000354
reed@google.com18c464b2012-05-11 20:57:25 +0000355 SkRasterClip rc0(ir);
356 SkRasterClip rc1(ir);
357 SkRasterClip rc2(ir);
358
senorblancoafc7cce2016-02-02 18:44:15 -0800359 rc0.op(r, baseBounds, SkRegion::kIntersect_Op, false);
reed@google.com18c464b2012-05-11 20:57:25 +0000360 r.offset(dx[i], 0);
senorblancoafc7cce2016-02-02 18:44:15 -0800361 rc1.op(r, baseBounds, SkRegion::kIntersect_Op, true);
reed@google.com18c464b2012-05-11 20:57:25 +0000362 r.offset(-2*dx[i], 0);
senorblancoafc7cce2016-02-02 18:44:15 -0800363 rc2.op(r, baseBounds, SkRegion::kIntersect_Op, true);
rmistry@google.comd6176b02012-08-23 18:14:13 +0000364
reed@google.com18c464b2012-05-11 20:57:25 +0000365 REPORTER_ASSERT(reporter, changed != (rc0 == rc1));
366 REPORTER_ASSERT(reporter, changed != (rc0 == rc2));
367 }
368}
369
370static void test_nearly_integral(skiatest::Reporter* reporter) {
371 // All of these should generate equivalent rasterclips
rmistry@google.comd6176b02012-08-23 18:14:13 +0000372
reed@google.com18c464b2012-05-11 20:57:25 +0000373 static const SkScalar gSafeX[] = {
374 0, SK_Scalar1/1000, SK_Scalar1/100, SK_Scalar1/10,
375 };
376 did_dx_affect(reporter, gSafeX, SK_ARRAY_COUNT(gSafeX), false);
377
378 static const SkScalar gUnsafeX[] = {
379 SK_Scalar1/4, SK_Scalar1/3,
380 };
381 did_dx_affect(reporter, gUnsafeX, SK_ARRAY_COUNT(gUnsafeX), true);
382}
383
sugoi@google.com54f0d1b2013-02-27 19:17:41 +0000384static void test_regressions() {
reed@google.com9b0da232012-02-29 13:59:15 +0000385 // these should not assert in the debug build
386 // bug was introduced in rev. 3209
387 {
388 SkAAClip clip;
389 SkRect r;
commit-bot@chromium.org4b413c82013-11-25 19:44:07 +0000390 r.fLeft = 129.892181f;
391 r.fTop = 10.3999996f;
392 r.fRight = 130.892181f;
393 r.fBottom = 20.3999996f;
reed@google.com9b0da232012-02-29 13:59:15 +0000394 clip.setRect(r, true);
395 }
396}
397
reed157f36d2014-10-15 07:05:09 -0700398// Building aaclip meant aa-scan-convert a path into a huge clip.
399// the old algorithm sized the supersampler to the size of the clip, which overflowed
400// its internal 16bit coordinates. The fix was to intersect the clip+path_bounds before
401// sizing the supersampler.
402//
403// Before the fix, the following code would assert in debug builds.
404//
405static void test_crbug_422693(skiatest::Reporter* reporter) {
reed157f36d2014-10-15 07:05:09 -0700406 SkRasterClip rc(SkIRect::MakeLTRB(-25000, -25000, 25000, 25000));
407 SkPath path;
408 path.addCircle(50, 50, 50);
senorblancoafc7cce2016-02-02 18:44:15 -0800409 rc.op(path, rc.getBounds(), SkRegion::kIntersect_Op, true);
reed157f36d2014-10-15 07:05:09 -0700410}
411
mtklein@google.com014f2c42013-09-19 20:56:46 +0000412DEF_TEST(AAClip, reporter) {
reed@google.comd8676d22011-10-26 18:01:25 +0000413 test_empty(reporter);
414 test_path_bounds(reporter);
415 test_irect(reporter);
mike@reedtribe.org95b85bd2011-11-23 03:12:58 +0000416 test_rgn(reporter);
reed@google.com80cdb9a2012-02-16 18:56:17 +0000417 test_path_with_hole(reporter);
sugoi@google.com54f0d1b2013-02-27 19:17:41 +0000418 test_regressions();
reed@google.com18c464b2012-05-11 20:57:25 +0000419 test_nearly_integral(reporter);
reed202ab2a2014-08-07 11:48:10 -0700420 test_really_a_rect(reporter);
reed157f36d2014-10-15 07:05:09 -0700421 test_crbug_422693(reporter);
reed@google.com209c4152011-10-26 15:03:48 +0000422}