blob: 4817c06738a34ff8567083357aa748d7c7f249fd [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:
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
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
90static 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
98static 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
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) {
131 SkRandom rand;
132 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
235static 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
243static void test_irect(skiatest::Reporter* reporter) {
244 SkRandom rand;
245
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());
reed@google.com80cdb9a2012-02-16 18:56:17 +0000275
276 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);
310
311 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.com9b0da232012-02-29 13:59:15 +0000319static void test_regressions(skiatest::Reporter* reporter) {
320 // these should not assert in the debug build
321 // bug was introduced in rev. 3209
322 {
323 SkAAClip clip;
324 SkRect r;
325 r.fLeft = SkFloatToScalar(129.892181);
326 r.fTop = SkFloatToScalar(10.3999996);
327 r.fRight = SkFloatToScalar(130.892181);
328 r.fBottom = SkFloatToScalar(20.3999996);
329 clip.setRect(r, true);
330 }
331}
332
reed@google.com209c4152011-10-26 15:03:48 +0000333static void TestAAClip(skiatest::Reporter* reporter) {
reed@google.comd8676d22011-10-26 18:01:25 +0000334 test_empty(reporter);
335 test_path_bounds(reporter);
336 test_irect(reporter);
mike@reedtribe.org95b85bd2011-11-23 03:12:58 +0000337 test_rgn(reporter);
reed@google.com80cdb9a2012-02-16 18:56:17 +0000338 test_path_with_hole(reporter);
reed@google.com9b0da232012-02-29 13:59:15 +0000339 test_regressions(reporter);
reed@google.com209c4152011-10-26 15:03:48 +0000340}
341
342#include "TestClassDef.h"
343DEFINE_TESTCLASS("AAClip", AAClipTestClass, TestAAClip)