blob: cadd1806cf342c2db523fc7a5e65c3d6e6646f4f [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"
tfarina@chromium.org8f6884a2014-01-24 20:56:26 +000013#include "Test.h"
reed@google.comd8676d22011-10-26 18:01:25 +000014
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
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.orgfa9e5fa2014-02-13 22:00:04 +000079 bitmap.installPixels(info, mask->fImage, mask->fRowBytes, NULL, NULL);
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());
188 clip.setPath(path, NULL, true);
189 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());
202 clip.setPath(path, NULL, true);
203 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;
231 mask.fImage = NULL;
232 clip0.copyToMask(&mask);
233 REPORTER_ASSERT(reporter, NULL == mask.fImage);
234 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()) {
266 SkDebugf("[%d %d %d %d] %s [%d %d %d %d] = BW:[%d %d %d %d] AA:[%d %d %d %d]\n",
267 r0.fLeft, r0.fTop, r0.right(), r0.bottom(),
268 gRgnOpNames[j],
269 r1.fLeft, r1.fTop, r1.right(), r1.bottom(),
270 rgn2.getBounds().fLeft, rgn2.getBounds().fTop,
271 rgn2.getBounds().right(), rgn2.getBounds().bottom(),
272 clip2.getBounds().fLeft, clip2.getBounds().fTop,
273 clip2.getBounds().right(), clip2.getBounds().bottom());
274 }
275 REPORTER_ASSERT(reporter, nonEmptyAA == nonEmptyBW);
276 REPORTER_ASSERT(reporter, clip2.getBounds() == rgn2.getBounds());
rmistry@google.comd6176b02012-08-23 18:14:13 +0000277
reed@google.com80cdb9a2012-02-16 18:56:17 +0000278 SkMask maskBW, maskAA;
279 copyToMask(rgn2, &maskBW);
280 clip2.copyToMask(&maskAA);
tomhudson@google.com0435f162012-03-15 18:16:39 +0000281 SkAutoMaskFreeImage freeBW(maskBW.fImage);
282 SkAutoMaskFreeImage freeAA(maskAA.fImage);
reed@google.com80cdb9a2012-02-16 18:56:17 +0000283 REPORTER_ASSERT(reporter, maskBW == maskAA);
reed@google.comd8676d22011-10-26 18:01:25 +0000284 }
285 }
286}
287
reed@google.com80cdb9a2012-02-16 18:56:17 +0000288static void test_path_with_hole(skiatest::Reporter* reporter) {
289 static const uint8_t gExpectedImage[] = {
290 0xFF, 0xFF, 0xFF, 0xFF,
291 0xFF, 0xFF, 0xFF, 0xFF,
292 0x00, 0x00, 0x00, 0x00,
293 0x00, 0x00, 0x00, 0x00,
294 0xFF, 0xFF, 0xFF, 0xFF,
295 0xFF, 0xFF, 0xFF, 0xFF,
296 };
297 SkMask expected;
298 expected.fBounds.set(0, 0, 4, 6);
299 expected.fRowBytes = 4;
300 expected.fFormat = SkMask::kA8_Format;
301 expected.fImage = (uint8_t*)gExpectedImage;
302
303 SkPath path;
304 path.addRect(SkRect::MakeXYWH(0, 0,
305 SkIntToScalar(4), SkIntToScalar(2)));
306 path.addRect(SkRect::MakeXYWH(0, SkIntToScalar(4),
307 SkIntToScalar(4), SkIntToScalar(2)));
308
309 for (int i = 0; i < 2; ++i) {
310 SkAAClip clip;
311 clip.setPath(path, NULL, 1 == i);
rmistry@google.comd6176b02012-08-23 18:14:13 +0000312
reed@google.com80cdb9a2012-02-16 18:56:17 +0000313 SkMask mask;
314 clip.copyToMask(&mask);
tomhudson@google.com0435f162012-03-15 18:16:39 +0000315 SkAutoMaskFreeImage freeM(mask.fImage);
316
reed@google.com80cdb9a2012-02-16 18:56:17 +0000317 REPORTER_ASSERT(reporter, expected == mask);
318 }
319}
320
reed@google.com18c464b2012-05-11 20:57:25 +0000321#include "SkRasterClip.h"
322
323static void copyToMask(const SkRasterClip& rc, SkMask* mask) {
324 if (rc.isAA()) {
325 rc.aaRgn().copyToMask(mask);
326 } else {
327 copyToMask(rc.bwRgn(), mask);
328 }
329}
330
331static bool operator==(const SkRasterClip& a, const SkRasterClip& b) {
332 if (a.isEmpty()) {
333 return b.isEmpty();
334 }
335 if (b.isEmpty()) {
336 return false;
337 }
338
339 SkMask ma, mb;
340 copyToMask(a, &ma);
341 copyToMask(b, &mb);
robertphillips@google.coma22e2112012-08-16 14:58:06 +0000342 SkAutoMaskFreeImage aCleanUp(ma.fImage);
343 SkAutoMaskFreeImage bCleanUp(mb.fImage);
344
reed@google.com18c464b2012-05-11 20:57:25 +0000345 return ma == mb;
346}
347
348static void did_dx_affect(skiatest::Reporter* reporter, const SkScalar dx[],
349 size_t count, bool changed) {
350 SkIRect ir = { 0, 0, 10, 10 };
351
352 for (size_t i = 0; i < count; ++i) {
353 SkRect r;
354 r.set(ir);
rmistry@google.comd6176b02012-08-23 18:14:13 +0000355
reed@google.com18c464b2012-05-11 20:57:25 +0000356 SkRasterClip rc0(ir);
357 SkRasterClip rc1(ir);
358 SkRasterClip rc2(ir);
359
360 rc0.op(r, SkRegion::kIntersect_Op, false);
361 r.offset(dx[i], 0);
362 rc1.op(r, SkRegion::kIntersect_Op, true);
363 r.offset(-2*dx[i], 0);
364 rc2.op(r, SkRegion::kIntersect_Op, true);
rmistry@google.comd6176b02012-08-23 18:14:13 +0000365
reed@google.com18c464b2012-05-11 20:57:25 +0000366 REPORTER_ASSERT(reporter, changed != (rc0 == rc1));
367 REPORTER_ASSERT(reporter, changed != (rc0 == rc2));
368 }
369}
370
371static void test_nearly_integral(skiatest::Reporter* reporter) {
372 // All of these should generate equivalent rasterclips
rmistry@google.comd6176b02012-08-23 18:14:13 +0000373
reed@google.com18c464b2012-05-11 20:57:25 +0000374 static const SkScalar gSafeX[] = {
375 0, SK_Scalar1/1000, SK_Scalar1/100, SK_Scalar1/10,
376 };
377 did_dx_affect(reporter, gSafeX, SK_ARRAY_COUNT(gSafeX), false);
378
379 static const SkScalar gUnsafeX[] = {
380 SK_Scalar1/4, SK_Scalar1/3,
381 };
382 did_dx_affect(reporter, gUnsafeX, SK_ARRAY_COUNT(gUnsafeX), true);
383}
384
sugoi@google.com54f0d1b2013-02-27 19:17:41 +0000385static void test_regressions() {
reed@google.com9b0da232012-02-29 13:59:15 +0000386 // these should not assert in the debug build
387 // bug was introduced in rev. 3209
388 {
389 SkAAClip clip;
390 SkRect r;
commit-bot@chromium.org4b413c82013-11-25 19:44:07 +0000391 r.fLeft = 129.892181f;
392 r.fTop = 10.3999996f;
393 r.fRight = 130.892181f;
394 r.fBottom = 20.3999996f;
reed@google.com9b0da232012-02-29 13:59:15 +0000395 clip.setRect(r, true);
396 }
397}
398
mtklein@google.com014f2c42013-09-19 20:56:46 +0000399DEF_TEST(AAClip, reporter) {
reed@google.comd8676d22011-10-26 18:01:25 +0000400 test_empty(reporter);
401 test_path_bounds(reporter);
402 test_irect(reporter);
mike@reedtribe.org95b85bd2011-11-23 03:12:58 +0000403 test_rgn(reporter);
reed@google.com80cdb9a2012-02-16 18:56:17 +0000404 test_path_with_hole(reporter);
sugoi@google.com54f0d1b2013-02-27 19:17:41 +0000405 test_regressions();
reed@google.com18c464b2012-05-11 20:57:25 +0000406 test_nearly_integral(reporter);
reed@google.com209c4152011-10-26 15:03:48 +0000407}