blob: f70cfd679ff669fe0fb9924fc79b4d63192e7ae3 [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
8#include "Test.h"
9#include "SkAAClip.h"
mike@reedtribe.org95b85bd2011-11-23 03:12:58 +000010#include "SkCanvas.h"
11#include "SkMask.h"
reed@google.com209c4152011-10-26 15:03:48 +000012#include "SkPath.h"
reed@google.comd8676d22011-10-26 18:01:25 +000013#include "SkRandom.h"
14
mike@reedtribe.org95b85bd2011-11-23 03:12:58 +000015static 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:
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;
reed@google.coma052aca2011-11-23 19:43:46 +000065 mask->fImage = NULL;
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
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
commit-bot@chromium.orge0e7cfe2013-09-09 20:09:12 +000090static SkIRect rand_rect(SkRandom& rand, int n) {
mike@reedtribe.org95b85bd2011-11-23 03:12:58 +000091 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
commit-bot@chromium.orge0e7cfe2013-09-09 20:09:12 +000098static void make_rand_rgn(SkRegion* rgn, SkRandom& rand) {
mike@reedtribe.org95b85bd2011-11-23 03:12:58 +000099 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
reed@google.coma052aca2011-11-23 19:43:46 +0000105static 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
117static bool equalsAAClip(const SkRegion& rgn) {
118 SkAAClip aaclip;
119 aaclip.setRegion(rgn);
120 return rgn == aaclip;
121}
122
123static void setRgnToPath(SkRegion* rgn, const SkPath& path) {
124 SkIRect ir;
125 path.getBounds().round(&ir);
126 rgn->setPath(path, SkRegion(ir));
127}
128
mike@reedtribe.org95b85bd2011-11-23 03:12:58 +0000129// aaclip.setRegion should create idential masks to the region
130static void test_rgn(skiatest::Reporter* reporter) {
commit-bot@chromium.orge0e7cfe2013-09-09 20:09:12 +0000131 SkRandom rand;
mike@reedtribe.org95b85bd2011-11-23 03:12:58 +0000132 for (int i = 0; i < 1000; i++) {
133 SkRegion rgn;
134 make_rand_rgn(&rgn, rand);
reed@google.coma052aca2011-11-23 19:43:46 +0000135 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));
mike@reedtribe.org95b85bd2011-11-23 03:12:58 +0000152 }
153}
154
reed@google.comd8676d22011-10-26 18:01:25 +0000155static const SkRegion::Op gRgnOps[] = {
reed@google.comc9041912011-10-27 16:58:46 +0000156 SkRegion::kDifference_Op,
reed@google.comd8676d22011-10-26 18:01:25 +0000157 SkRegion::kIntersect_Op,
158 SkRegion::kUnion_Op,
159 SkRegion::kXOR_Op,
reed@google.comc9041912011-10-27 16:58:46 +0000160 SkRegion::kReverseDifference_Op,
reed@google.comd8676d22011-10-26 18:01:25 +0000161 SkRegion::kReplace_Op
162};
163
164static const char* gRgnOpNames[] = {
reed@google.comc9041912011-10-27 16:58:46 +0000165 "DIFF", "INTERSECT", "UNION", "XOR", "REVERSE_DIFF", "REPLACE"
reed@google.comd8676d22011-10-26 18:01:25 +0000166};
reed@google.com209c4152011-10-26 15:03:48 +0000167
reed@google.com12e15252011-10-26 15:19:36 +0000168static void imoveTo(SkPath& path, int x, int y) {
169 path.moveTo(SkIntToScalar(x), SkIntToScalar(y));
170}
171
172static 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
reed@google.comd8676d22011-10-26 18:01:25 +0000178static void test_path_bounds(skiatest::Reporter* reporter) {
reed@google.com209c4152011-10-26 15:03:48 +0000179 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();
reed@google.com12e15252011-10-26 15:19:36 +0000197 imoveTo(path, 0, 20);
198 icubicTo(path, 40, 40, 40, 0, 0, 20);
reed@google.com209c4152011-10-26 15:03:48 +0000199 REPORTER_ASSERT(reporter, sheight == path.getBounds().height());
200 clip.setPath(path, NULL, true);
201 REPORTER_ASSERT(reporter, teardrop_height == clip.getBounds().height());
202}
203
reed@google.comd8676d22011-10-26 18:01:25 +0000204static 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
commit-bot@chromium.orge0e7cfe2013-09-09 20:09:12 +0000235static void rand_irect(SkIRect* r, int N, SkRandom& rand) {
reed@google.comd8676d22011-10-26 18:01:25 +0000236 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
243static void test_irect(skiatest::Reporter* reporter) {
commit-bot@chromium.orge0e7cfe2013-09-09 20:09:12 +0000244 SkRandom rand;
reed@google.comd8676d22011-10-26 18:01:25 +0000245
reed@google.comc9041912011-10-27 16:58:46 +0000246 for (int i = 0; i < 10000; i++) {
reed@google.comd8676d22011-10-26 18:01:25 +0000247 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());
rmistry@google.comd6176b02012-08-23 18:14:13 +0000275
reed@google.com80cdb9a2012-02-16 18:56:17 +0000276 SkMask maskBW, maskAA;
277 copyToMask(rgn2, &maskBW);
278 clip2.copyToMask(&maskAA);
tomhudson@google.com0435f162012-03-15 18:16:39 +0000279 SkAutoMaskFreeImage freeBW(maskBW.fImage);
280 SkAutoMaskFreeImage freeAA(maskAA.fImage);
reed@google.com80cdb9a2012-02-16 18:56:17 +0000281 REPORTER_ASSERT(reporter, maskBW == maskAA);
reed@google.comd8676d22011-10-26 18:01:25 +0000282 }
283 }
284}
285
reed@google.com80cdb9a2012-02-16 18:56:17 +0000286static 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);
rmistry@google.comd6176b02012-08-23 18:14:13 +0000310
reed@google.com80cdb9a2012-02-16 18:56:17 +0000311 SkMask mask;
312 clip.copyToMask(&mask);
tomhudson@google.com0435f162012-03-15 18:16:39 +0000313 SkAutoMaskFreeImage freeM(mask.fImage);
314
reed@google.com80cdb9a2012-02-16 18:56:17 +0000315 REPORTER_ASSERT(reporter, expected == mask);
316 }
317}
318
reed@google.com18c464b2012-05-11 20:57:25 +0000319#include "SkRasterClip.h"
320
321static 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
329static 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);
robertphillips@google.coma22e2112012-08-16 14:58:06 +0000340 SkAutoMaskFreeImage aCleanUp(ma.fImage);
341 SkAutoMaskFreeImage bCleanUp(mb.fImage);
342
reed@google.com18c464b2012-05-11 20:57:25 +0000343 return ma == mb;
344}
345
346static 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);
rmistry@google.comd6176b02012-08-23 18:14:13 +0000353
reed@google.com18c464b2012-05-11 20:57:25 +0000354 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);
rmistry@google.comd6176b02012-08-23 18:14:13 +0000363
reed@google.com18c464b2012-05-11 20:57:25 +0000364 REPORTER_ASSERT(reporter, changed != (rc0 == rc1));
365 REPORTER_ASSERT(reporter, changed != (rc0 == rc2));
366 }
367}
368
369static void test_nearly_integral(skiatest::Reporter* reporter) {
370 // All of these should generate equivalent rasterclips
rmistry@google.comd6176b02012-08-23 18:14:13 +0000371
reed@google.com18c464b2012-05-11 20:57:25 +0000372 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
sugoi@google.com54f0d1b2013-02-27 19:17:41 +0000383static void test_regressions() {
reed@google.com9b0da232012-02-29 13:59:15 +0000384 // these should not assert in the debug build
385 // bug was introduced in rev. 3209
386 {
387 SkAAClip clip;
388 SkRect r;
robertphillips@google.com6853e802012-04-16 15:50:18 +0000389 r.fLeft = SkFloatToScalar(129.892181f);
390 r.fTop = SkFloatToScalar(10.3999996f);
rmistry@google.comd6176b02012-08-23 18:14:13 +0000391 r.fRight = SkFloatToScalar(130.892181f);
robertphillips@google.com6853e802012-04-16 15:50:18 +0000392 r.fBottom = SkFloatToScalar(20.3999996f);
reed@google.com9b0da232012-02-29 13:59:15 +0000393 clip.setRect(r, true);
394 }
395}
396
mtklein@google.com014f2c42013-09-19 20:56:46 +0000397#include "TestClassDef.h"
398DEF_TEST(AAClip, reporter) {
reed@google.comd8676d22011-10-26 18:01:25 +0000399 test_empty(reporter);
400 test_path_bounds(reporter);
401 test_irect(reporter);
mike@reedtribe.org95b85bd2011-11-23 03:12:58 +0000402 test_rgn(reporter);
reed@google.com80cdb9a2012-02-16 18:56:17 +0000403 test_path_with_hole(reporter);
sugoi@google.com54f0d1b2013-02-27 19:17:41 +0000404 test_regressions();
reed@google.com18c464b2012-05-11 20:57:25 +0000405 test_nearly_integral(reporter);
reed@google.com209c4152011-10-26 15:03:48 +0000406}