blob: 1ee6358a6eb1944712a96c10822950469a88490b [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"
bungemand3ebb482015-08-05 13:57:49 -070013#include "SkRRect.h"
tfarina@chromium.org8f6884a2014-01-24 20:56:26 +000014#include "Test.h"
reed@google.comd8676d22011-10-26 18:01:25 +000015
mike@reedtribe.org95b85bd2011-11-23 03:12:58 +000016static bool operator==(const SkMask& a, const SkMask& b) {
17 if (a.fFormat != b.fFormat || a.fBounds != b.fBounds) {
18 return false;
19 }
20 if (!a.fImage && !b.fImage) {
21 return true;
22 }
23 if (!a.fImage || !b.fImage) {
24 return false;
25 }
26
27 size_t wbytes = a.fBounds.width();
28 switch (a.fFormat) {
29 case SkMask::kBW_Format:
30 wbytes = (wbytes + 7) >> 3;
31 break;
32 case SkMask::kA8_Format:
33 case SkMask::k3D_Format:
34 break;
35 case SkMask::kLCD16_Format:
36 wbytes <<= 1;
37 break;
mike@reedtribe.org95b85bd2011-11-23 03:12:58 +000038 case SkMask::kARGB32_Format:
39 wbytes <<= 2;
40 break;
41 default:
mtklein@google.com330313a2013-08-22 15:37:26 +000042 SkDEBUGFAIL("unknown mask format");
mike@reedtribe.org95b85bd2011-11-23 03:12:58 +000043 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
59static void copyToMask(const SkRegion& rgn, SkMask* mask) {
reed@google.coma052aca2011-11-23 19:43:46 +000060 mask->fFormat = SkMask::kA8_Format;
61
mike@reedtribe.org95b85bd2011-11-23 03:12:58 +000062 if (rgn.isEmpty()) {
mike@reedtribe.org95b85bd2011-11-23 03:12:58 +000063 mask->fBounds.setEmpty();
64 mask->fRowBytes = 0;
halcanary96fcdcc2015-08-27 07:41:13 -070065 mask->fImage = nullptr;
mike@reedtribe.org95b85bd2011-11-23 03:12:58 +000066 return;
67 }
68
69 mask->fBounds = rgn.getBounds();
70 mask->fRowBytes = mask->fBounds.width();
mike@reedtribe.org95b85bd2011-11-23 03:12:58 +000071 mask->fImage = SkMask::AllocImage(mask->computeImageSize());
72 sk_bzero(mask->fImage, mask->computeImageSize());
73
commit-bot@chromium.orgfa9e5fa2014-02-13 22:00:04 +000074 SkImageInfo info = SkImageInfo::Make(mask->fBounds.width(),
75 mask->fBounds.height(),
76 kAlpha_8_SkColorType,
77 kPremul_SkAlphaType);
mike@reedtribe.org95b85bd2011-11-23 03:12:58 +000078 SkBitmap bitmap;
commit-bot@chromium.org00f8d6c2014-05-29 15:57:20 +000079 bitmap.installPixels(info, mask->fImage, mask->fRowBytes);
mike@reedtribe.org95b85bd2011-11-23 03:12:58 +000080
81 // canvas expects its coordinate system to always be 0,0 in the top/left
82 // so we translate the rgn to match that before drawing into the mask.
83 //
84 SkRegion tmpRgn(rgn);
85 tmpRgn.translate(-rgn.getBounds().fLeft, -rgn.getBounds().fTop);
86
87 SkCanvas canvas(bitmap);
88 canvas.clipRegion(tmpRgn);
89 canvas.drawColor(SK_ColorBLACK);
90}
91
commit-bot@chromium.orge0e7cfe2013-09-09 20:09:12 +000092static SkIRect rand_rect(SkRandom& rand, int n) {
mike@reedtribe.org95b85bd2011-11-23 03:12:58 +000093 int x = rand.nextS() % n;
94 int y = rand.nextS() % n;
95 int w = rand.nextU() % n;
96 int h = rand.nextU() % n;
97 return SkIRect::MakeXYWH(x, y, w, h);
98}
99
commit-bot@chromium.orge0e7cfe2013-09-09 20:09:12 +0000100static void make_rand_rgn(SkRegion* rgn, SkRandom& rand) {
mike@reedtribe.org95b85bd2011-11-23 03:12:58 +0000101 int count = rand.nextU() % 20;
102 for (int i = 0; i < count; ++i) {
103 rgn->op(rand_rect(rand, 100), SkRegion::kXOR_Op);
104 }
105}
106
reed@google.coma052aca2011-11-23 19:43:46 +0000107static bool operator==(const SkRegion& rgn, const SkAAClip& aaclip) {
108 SkMask mask0, mask1;
109
110 copyToMask(rgn, &mask0);
111 aaclip.copyToMask(&mask1);
112 bool eq = (mask0 == mask1);
113
114 SkMask::FreeImage(mask0.fImage);
115 SkMask::FreeImage(mask1.fImage);
116 return eq;
117}
118
119static bool equalsAAClip(const SkRegion& rgn) {
120 SkAAClip aaclip;
121 aaclip.setRegion(rgn);
122 return rgn == aaclip;
123}
124
125static void setRgnToPath(SkRegion* rgn, const SkPath& path) {
126 SkIRect ir;
127 path.getBounds().round(&ir);
128 rgn->setPath(path, SkRegion(ir));
129}
130
mike@reedtribe.org95b85bd2011-11-23 03:12:58 +0000131// aaclip.setRegion should create idential masks to the region
132static void test_rgn(skiatest::Reporter* reporter) {
commit-bot@chromium.orge0e7cfe2013-09-09 20:09:12 +0000133 SkRandom rand;
mike@reedtribe.org95b85bd2011-11-23 03:12:58 +0000134 for (int i = 0; i < 1000; i++) {
135 SkRegion rgn;
136 make_rand_rgn(&rgn, rand);
reed@google.coma052aca2011-11-23 19:43:46 +0000137 REPORTER_ASSERT(reporter, equalsAAClip(rgn));
138 }
139
140 {
141 SkRegion rgn;
142 SkPath path;
143 path.addCircle(0, 0, SkIntToScalar(30));
144 setRgnToPath(&rgn, path);
145 REPORTER_ASSERT(reporter, equalsAAClip(rgn));
146
147 path.reset();
148 path.moveTo(0, 0);
149 path.lineTo(SkIntToScalar(100), 0);
150 path.lineTo(SkIntToScalar(100 - 20), SkIntToScalar(20));
151 path.lineTo(SkIntToScalar(20), SkIntToScalar(20));
152 setRgnToPath(&rgn, path);
153 REPORTER_ASSERT(reporter, equalsAAClip(rgn));
mike@reedtribe.org95b85bd2011-11-23 03:12:58 +0000154 }
155}
156
reed@google.comd8676d22011-10-26 18:01:25 +0000157static const SkRegion::Op gRgnOps[] = {
reed@google.comc9041912011-10-27 16:58:46 +0000158 SkRegion::kDifference_Op,
reed@google.comd8676d22011-10-26 18:01:25 +0000159 SkRegion::kIntersect_Op,
160 SkRegion::kUnion_Op,
161 SkRegion::kXOR_Op,
reed@google.comc9041912011-10-27 16:58:46 +0000162 SkRegion::kReverseDifference_Op,
reed@google.comd8676d22011-10-26 18:01:25 +0000163 SkRegion::kReplace_Op
164};
165
166static const char* gRgnOpNames[] = {
reed@google.comc9041912011-10-27 16:58:46 +0000167 "DIFF", "INTERSECT", "UNION", "XOR", "REVERSE_DIFF", "REPLACE"
reed@google.comd8676d22011-10-26 18:01:25 +0000168};
reed@google.com209c4152011-10-26 15:03:48 +0000169
reed@google.com12e15252011-10-26 15:19:36 +0000170static void imoveTo(SkPath& path, int x, int y) {
171 path.moveTo(SkIntToScalar(x), SkIntToScalar(y));
172}
173
174static void icubicTo(SkPath& path, int x0, int y0, int x1, int y1, int x2, int y2) {
175 path.cubicTo(SkIntToScalar(x0), SkIntToScalar(y0),
176 SkIntToScalar(x1), SkIntToScalar(y1),
177 SkIntToScalar(x2), SkIntToScalar(y2));
178}
179
reed@google.comd8676d22011-10-26 18:01:25 +0000180static void test_path_bounds(skiatest::Reporter* reporter) {
reed@google.com209c4152011-10-26 15:03:48 +0000181 SkPath path;
182 SkAAClip clip;
183 const int height = 40;
184 const SkScalar sheight = SkIntToScalar(height);
185
186 path.addOval(SkRect::MakeWH(sheight, sheight));
187 REPORTER_ASSERT(reporter, sheight == path.getBounds().height());
halcanary96fcdcc2015-08-27 07:41:13 -0700188 clip.setPath(path, nullptr, true);
reed@google.com209c4152011-10-26 15:03:48 +0000189 REPORTER_ASSERT(reporter, height == clip.getBounds().height());
190
191 // this is the trimmed height of this cubic (with aa). The critical thing
192 // for this test is that it is less than height, which represents just
193 // the bounds of the path's control-points.
194 //
195 // This used to fail until we tracked the MinY in the BuilderBlitter.
196 //
197 const int teardrop_height = 12;
198 path.reset();
reed@google.com12e15252011-10-26 15:19:36 +0000199 imoveTo(path, 0, 20);
200 icubicTo(path, 40, 40, 40, 0, 0, 20);
reed@google.com209c4152011-10-26 15:03:48 +0000201 REPORTER_ASSERT(reporter, sheight == path.getBounds().height());
halcanary96fcdcc2015-08-27 07:41:13 -0700202 clip.setPath(path, nullptr, true);
reed@google.com209c4152011-10-26 15:03:48 +0000203 REPORTER_ASSERT(reporter, teardrop_height == clip.getBounds().height());
204}
205
reed@google.comd8676d22011-10-26 18:01:25 +0000206static void test_empty(skiatest::Reporter* reporter) {
207 SkAAClip clip0, clip1;
208
209 REPORTER_ASSERT(reporter, clip0.isEmpty());
210 REPORTER_ASSERT(reporter, clip0.getBounds().isEmpty());
211 REPORTER_ASSERT(reporter, clip1 == clip0);
212
213 clip0.translate(10, 10); // should have no effect on empty
214 REPORTER_ASSERT(reporter, clip0.isEmpty());
215 REPORTER_ASSERT(reporter, clip0.getBounds().isEmpty());
216 REPORTER_ASSERT(reporter, clip1 == clip0);
217
218 SkIRect r = { 10, 10, 40, 50 };
219 clip0.setRect(r);
220 REPORTER_ASSERT(reporter, !clip0.isEmpty());
221 REPORTER_ASSERT(reporter, !clip0.getBounds().isEmpty());
222 REPORTER_ASSERT(reporter, clip0 != clip1);
223 REPORTER_ASSERT(reporter, clip0.getBounds() == r);
224
225 clip0.setEmpty();
226 REPORTER_ASSERT(reporter, clip0.isEmpty());
227 REPORTER_ASSERT(reporter, clip0.getBounds().isEmpty());
228 REPORTER_ASSERT(reporter, clip1 == clip0);
229
230 SkMask mask;
reed@google.comd8676d22011-10-26 18:01:25 +0000231 clip0.copyToMask(&mask);
halcanary96fcdcc2015-08-27 07:41:13 -0700232 REPORTER_ASSERT(reporter, nullptr == mask.fImage);
reed@google.comd8676d22011-10-26 18:01:25 +0000233 REPORTER_ASSERT(reporter, mask.fBounds.isEmpty());
234}
235
commit-bot@chromium.orge0e7cfe2013-09-09 20:09:12 +0000236static void rand_irect(SkIRect* r, int N, SkRandom& rand) {
reed@google.comd8676d22011-10-26 18:01:25 +0000237 r->setXYWH(0, 0, rand.nextU() % N, rand.nextU() % N);
238 int dx = rand.nextU() % (2*N);
239 int dy = rand.nextU() % (2*N);
240 // use int dx,dy to make the subtract be signed
241 r->offset(N - dx, N - dy);
242}
243
244static void test_irect(skiatest::Reporter* reporter) {
commit-bot@chromium.orge0e7cfe2013-09-09 20:09:12 +0000245 SkRandom rand;
reed@google.comd8676d22011-10-26 18:01:25 +0000246
reed@google.comc9041912011-10-27 16:58:46 +0000247 for (int i = 0; i < 10000; i++) {
reed@google.comd8676d22011-10-26 18:01:25 +0000248 SkAAClip clip0, clip1;
249 SkRegion rgn0, rgn1;
250 SkIRect r0, r1;
251
252 rand_irect(&r0, 10, rand);
253 rand_irect(&r1, 10, rand);
254 clip0.setRect(r0);
255 clip1.setRect(r1);
256 rgn0.setRect(r0);
257 rgn1.setRect(r1);
258 for (size_t j = 0; j < SK_ARRAY_COUNT(gRgnOps); ++j) {
259 SkRegion::Op op = gRgnOps[j];
260 SkAAClip clip2;
261 SkRegion rgn2;
262 bool nonEmptyAA = clip2.op(clip0, clip1, op);
263 bool nonEmptyBW = rgn2.op(rgn0, rgn1, op);
264 if (nonEmptyAA != nonEmptyBW || clip2.getBounds() != rgn2.getBounds()) {
265 SkDebugf("[%d %d %d %d] %s [%d %d %d %d] = BW:[%d %d %d %d] AA:[%d %d %d %d]\n",
266 r0.fLeft, r0.fTop, r0.right(), r0.bottom(),
267 gRgnOpNames[j],
268 r1.fLeft, r1.fTop, r1.right(), r1.bottom(),
269 rgn2.getBounds().fLeft, rgn2.getBounds().fTop,
270 rgn2.getBounds().right(), rgn2.getBounds().bottom(),
271 clip2.getBounds().fLeft, clip2.getBounds().fTop,
272 clip2.getBounds().right(), clip2.getBounds().bottom());
273 }
274 REPORTER_ASSERT(reporter, nonEmptyAA == nonEmptyBW);
275 REPORTER_ASSERT(reporter, clip2.getBounds() == rgn2.getBounds());
rmistry@google.comd6176b02012-08-23 18:14:13 +0000276
reed@google.com80cdb9a2012-02-16 18:56:17 +0000277 SkMask maskBW, maskAA;
278 copyToMask(rgn2, &maskBW);
279 clip2.copyToMask(&maskAA);
tomhudson@google.com0435f162012-03-15 18:16:39 +0000280 SkAutoMaskFreeImage freeBW(maskBW.fImage);
281 SkAutoMaskFreeImage freeAA(maskAA.fImage);
reed@google.com80cdb9a2012-02-16 18:56:17 +0000282 REPORTER_ASSERT(reporter, maskBW == maskAA);
reed@google.comd8676d22011-10-26 18:01:25 +0000283 }
284 }
285}
286
reed@google.com80cdb9a2012-02-16 18:56:17 +0000287static void test_path_with_hole(skiatest::Reporter* reporter) {
288 static const uint8_t gExpectedImage[] = {
289 0xFF, 0xFF, 0xFF, 0xFF,
290 0xFF, 0xFF, 0xFF, 0xFF,
291 0x00, 0x00, 0x00, 0x00,
292 0x00, 0x00, 0x00, 0x00,
293 0xFF, 0xFF, 0xFF, 0xFF,
294 0xFF, 0xFF, 0xFF, 0xFF,
295 };
296 SkMask expected;
297 expected.fBounds.set(0, 0, 4, 6);
298 expected.fRowBytes = 4;
299 expected.fFormat = SkMask::kA8_Format;
300 expected.fImage = (uint8_t*)gExpectedImage;
301
302 SkPath path;
303 path.addRect(SkRect::MakeXYWH(0, 0,
304 SkIntToScalar(4), SkIntToScalar(2)));
305 path.addRect(SkRect::MakeXYWH(0, SkIntToScalar(4),
306 SkIntToScalar(4), SkIntToScalar(2)));
307
308 for (int i = 0; i < 2; ++i) {
309 SkAAClip clip;
halcanary96fcdcc2015-08-27 07:41:13 -0700310 clip.setPath(path, nullptr, 1 == i);
rmistry@google.comd6176b02012-08-23 18:14:13 +0000311
reed@google.com80cdb9a2012-02-16 18:56:17 +0000312 SkMask mask;
313 clip.copyToMask(&mask);
tomhudson@google.com0435f162012-03-15 18:16:39 +0000314 SkAutoMaskFreeImage freeM(mask.fImage);
315
reed@google.com80cdb9a2012-02-16 18:56:17 +0000316 REPORTER_ASSERT(reporter, expected == mask);
317 }
318}
319
reed202ab2a2014-08-07 11:48:10 -0700320static void test_really_a_rect(skiatest::Reporter* reporter) {
321 SkRRect rrect;
322 rrect.setRectXY(SkRect::MakeWH(100, 100), 5, 5);
323
324 SkPath path;
325 path.addRRect(rrect);
326
327 SkAAClip clip;
328 clip.setPath(path);
329
330 REPORTER_ASSERT(reporter, clip.getBounds() == SkIRect::MakeWH(100, 100));
331 REPORTER_ASSERT(reporter, !clip.isRect());
332
333 // This rect should intersect the clip, but slice-out all of the "soft" parts,
334 // leaving just a rect.
335 const SkIRect ir = SkIRect::MakeLTRB(10, -10, 50, 90);
336
337 clip.op(ir, SkRegion::kIntersect_Op);
338
339 REPORTER_ASSERT(reporter, clip.getBounds() == SkIRect::MakeLTRB(10, 0, 50, 90));
340 // the clip recognized that that it is just a rect!
341 REPORTER_ASSERT(reporter, clip.isRect());
342}
343
reed@google.com18c464b2012-05-11 20:57:25 +0000344#include "SkRasterClip.h"
345
346static void copyToMask(const SkRasterClip& rc, SkMask* mask) {
347 if (rc.isAA()) {
348 rc.aaRgn().copyToMask(mask);
349 } else {
350 copyToMask(rc.bwRgn(), mask);
351 }
352}
353
354static bool operator==(const SkRasterClip& a, const SkRasterClip& b) {
355 if (a.isEmpty()) {
356 return b.isEmpty();
357 }
358 if (b.isEmpty()) {
359 return false;
360 }
361
362 SkMask ma, mb;
363 copyToMask(a, &ma);
364 copyToMask(b, &mb);
robertphillips@google.coma22e2112012-08-16 14:58:06 +0000365 SkAutoMaskFreeImage aCleanUp(ma.fImage);
366 SkAutoMaskFreeImage bCleanUp(mb.fImage);
367
reed@google.com18c464b2012-05-11 20:57:25 +0000368 return ma == mb;
369}
370
371static void did_dx_affect(skiatest::Reporter* reporter, const SkScalar dx[],
372 size_t count, bool changed) {
reedd9544982014-09-09 18:46:22 -0700373 const SkISize baseSize = SkISize::Make(10, 10);
reed@google.com18c464b2012-05-11 20:57:25 +0000374 SkIRect ir = { 0, 0, 10, 10 };
375
376 for (size_t i = 0; i < count; ++i) {
377 SkRect r;
378 r.set(ir);
rmistry@google.comd6176b02012-08-23 18:14:13 +0000379
reed@google.com18c464b2012-05-11 20:57:25 +0000380 SkRasterClip rc0(ir);
381 SkRasterClip rc1(ir);
382 SkRasterClip rc2(ir);
383
reedd9544982014-09-09 18:46:22 -0700384 rc0.op(r, baseSize, SkRegion::kIntersect_Op, false);
reed@google.com18c464b2012-05-11 20:57:25 +0000385 r.offset(dx[i], 0);
reedd9544982014-09-09 18:46:22 -0700386 rc1.op(r, baseSize, SkRegion::kIntersect_Op, true);
reed@google.com18c464b2012-05-11 20:57:25 +0000387 r.offset(-2*dx[i], 0);
reedd9544982014-09-09 18:46:22 -0700388 rc2.op(r, baseSize, SkRegion::kIntersect_Op, true);
rmistry@google.comd6176b02012-08-23 18:14:13 +0000389
reed@google.com18c464b2012-05-11 20:57:25 +0000390 REPORTER_ASSERT(reporter, changed != (rc0 == rc1));
391 REPORTER_ASSERT(reporter, changed != (rc0 == rc2));
392 }
393}
394
395static void test_nearly_integral(skiatest::Reporter* reporter) {
396 // All of these should generate equivalent rasterclips
rmistry@google.comd6176b02012-08-23 18:14:13 +0000397
reed@google.com18c464b2012-05-11 20:57:25 +0000398 static const SkScalar gSafeX[] = {
399 0, SK_Scalar1/1000, SK_Scalar1/100, SK_Scalar1/10,
400 };
401 did_dx_affect(reporter, gSafeX, SK_ARRAY_COUNT(gSafeX), false);
402
403 static const SkScalar gUnsafeX[] = {
404 SK_Scalar1/4, SK_Scalar1/3,
405 };
406 did_dx_affect(reporter, gUnsafeX, SK_ARRAY_COUNT(gUnsafeX), true);
407}
408
sugoi@google.com54f0d1b2013-02-27 19:17:41 +0000409static void test_regressions() {
reed@google.com9b0da232012-02-29 13:59:15 +0000410 // these should not assert in the debug build
411 // bug was introduced in rev. 3209
412 {
413 SkAAClip clip;
414 SkRect r;
commit-bot@chromium.org4b413c82013-11-25 19:44:07 +0000415 r.fLeft = 129.892181f;
416 r.fTop = 10.3999996f;
417 r.fRight = 130.892181f;
418 r.fBottom = 20.3999996f;
reed@google.com9b0da232012-02-29 13:59:15 +0000419 clip.setRect(r, true);
420 }
421}
422
reed157f36d2014-10-15 07:05:09 -0700423// Building aaclip meant aa-scan-convert a path into a huge clip.
424// the old algorithm sized the supersampler to the size of the clip, which overflowed
425// its internal 16bit coordinates. The fix was to intersect the clip+path_bounds before
426// sizing the supersampler.
427//
428// Before the fix, the following code would assert in debug builds.
429//
430static void test_crbug_422693(skiatest::Reporter* reporter) {
reed157f36d2014-10-15 07:05:09 -0700431 SkRasterClip rc(SkIRect::MakeLTRB(-25000, -25000, 25000, 25000));
432 SkPath path;
433 path.addCircle(50, 50, 50);
434 rc.op(path, rc.getBounds().size(), SkRegion::kIntersect_Op, true);
reed157f36d2014-10-15 07:05:09 -0700435}
436
mtklein@google.com014f2c42013-09-19 20:56:46 +0000437DEF_TEST(AAClip, reporter) {
reed@google.comd8676d22011-10-26 18:01:25 +0000438 test_empty(reporter);
439 test_path_bounds(reporter);
440 test_irect(reporter);
mike@reedtribe.org95b85bd2011-11-23 03:12:58 +0000441 test_rgn(reporter);
reed@google.com80cdb9a2012-02-16 18:56:17 +0000442 test_path_with_hole(reporter);
sugoi@google.com54f0d1b2013-02-27 19:17:41 +0000443 test_regressions();
reed@google.com18c464b2012-05-11 20:57:25 +0000444 test_nearly_integral(reporter);
reed202ab2a2014-08-07 11:48:10 -0700445 test_really_a_rect(reporter);
reed157f36d2014-10-15 07:05:09 -0700446 test_crbug_422693(reporter);
reed@google.com209c4152011-10-26 15:03:48 +0000447}