blob: b3051fdd826ab69e445cb4250c1b25ad7706de05 [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());
275 }
276 }
277}
278
reed@google.com209c4152011-10-26 15:03:48 +0000279static void TestAAClip(skiatest::Reporter* reporter) {
reed@google.comd8676d22011-10-26 18:01:25 +0000280 test_empty(reporter);
281 test_path_bounds(reporter);
282 test_irect(reporter);
mike@reedtribe.org95b85bd2011-11-23 03:12:58 +0000283 test_rgn(reporter);
reed@google.com209c4152011-10-26 15:03:48 +0000284}
285
286#include "TestClassDef.h"
287DEFINE_TESTCLASS("AAClip", AAClipTestClass, TestAAClip)