1 /*
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"
10 #include "SkCanvas.h"
11 #include "SkMask.h"
12 #include "SkPath.h"
13 #include "SkRandom.h"
14
operator ==(const SkMask & a,const SkMask & b)15 static 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
copyToMask(const SkRegion & rgn,SkMask * mask)59 static void copyToMask(const SkRegion& rgn, SkMask* mask) {
60 mask->fFormat = SkMask::kA8_Format;
61
62 if (rgn.isEmpty()) {
63 mask->fBounds.setEmpty();
64 mask->fRowBytes = 0;
65 mask->fImage = NULL;
66 return;
67 }
68
69 mask->fBounds = rgn.getBounds();
70 mask->fRowBytes = mask->fBounds.width();
71 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
rand_rect(SkRandom & rand,int n)90 static 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
make_rand_rgn(SkRegion * rgn,SkRandom & rand)98 static 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
operator ==(const SkRegion & rgn,const SkAAClip & aaclip)105 static 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
equalsAAClip(const SkRegion & rgn)117 static bool equalsAAClip(const SkRegion& rgn) {
118 SkAAClip aaclip;
119 aaclip.setRegion(rgn);
120 return rgn == aaclip;
121 }
122
setRgnToPath(SkRegion * rgn,const SkPath & path)123 static void setRgnToPath(SkRegion* rgn, const SkPath& path) {
124 SkIRect ir;
125 path.getBounds().round(&ir);
126 rgn->setPath(path, SkRegion(ir));
127 }
128
129 // aaclip.setRegion should create idential masks to the region
test_rgn(skiatest::Reporter * reporter)130 static 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);
135 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));
152 }
153 }
154
155 static const SkRegion::Op gRgnOps[] = {
156 SkRegion::kDifference_Op,
157 SkRegion::kIntersect_Op,
158 SkRegion::kUnion_Op,
159 SkRegion::kXOR_Op,
160 SkRegion::kReverseDifference_Op,
161 SkRegion::kReplace_Op
162 };
163
164 static const char* gRgnOpNames[] = {
165 "DIFF", "INTERSECT", "UNION", "XOR", "REVERSE_DIFF", "REPLACE"
166 };
167
imoveTo(SkPath & path,int x,int y)168 static void imoveTo(SkPath& path, int x, int y) {
169 path.moveTo(SkIntToScalar(x), SkIntToScalar(y));
170 }
171
icubicTo(SkPath & path,int x0,int y0,int x1,int y1,int x2,int y2)172 static 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
test_path_bounds(skiatest::Reporter * reporter)178 static void test_path_bounds(skiatest::Reporter* reporter) {
179 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();
197 imoveTo(path, 0, 20);
198 icubicTo(path, 40, 40, 40, 0, 0, 20);
199 REPORTER_ASSERT(reporter, sheight == path.getBounds().height());
200 clip.setPath(path, NULL, true);
201 REPORTER_ASSERT(reporter, teardrop_height == clip.getBounds().height());
202 }
203
test_empty(skiatest::Reporter * reporter)204 static 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
rand_irect(SkIRect * r,int N,SkRandom & rand)235 static 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
test_irect(skiatest::Reporter * reporter)243 static void test_irect(skiatest::Reporter* reporter) {
244 SkRandom rand;
245
246 for (int i = 0; i < 10000; i++) {
247 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 SkMask maskBW, maskAA;
277 copyToMask(rgn2, &maskBW);
278 clip2.copyToMask(&maskAA);
279 REPORTER_ASSERT(reporter, maskBW == maskAA);
280 }
281 }
282 }
283
test_path_with_hole(skiatest::Reporter * reporter)284 static void test_path_with_hole(skiatest::Reporter* reporter) {
285 static const uint8_t gExpectedImage[] = {
286 0xFF, 0xFF, 0xFF, 0xFF,
287 0xFF, 0xFF, 0xFF, 0xFF,
288 0x00, 0x00, 0x00, 0x00,
289 0x00, 0x00, 0x00, 0x00,
290 0xFF, 0xFF, 0xFF, 0xFF,
291 0xFF, 0xFF, 0xFF, 0xFF,
292 };
293 SkMask expected;
294 expected.fBounds.set(0, 0, 4, 6);
295 expected.fRowBytes = 4;
296 expected.fFormat = SkMask::kA8_Format;
297 expected.fImage = (uint8_t*)gExpectedImage;
298
299 SkPath path;
300 path.addRect(SkRect::MakeXYWH(0, 0,
301 SkIntToScalar(4), SkIntToScalar(2)));
302 path.addRect(SkRect::MakeXYWH(0, SkIntToScalar(4),
303 SkIntToScalar(4), SkIntToScalar(2)));
304
305 for (int i = 0; i < 2; ++i) {
306 SkAAClip clip;
307 clip.setPath(path, NULL, 1 == i);
308
309 SkMask mask;
310 clip.copyToMask(&mask);
311
312 REPORTER_ASSERT(reporter, expected == mask);
313 }
314 }
315
test_regressions(skiatest::Reporter * reporter)316 static void test_regressions(skiatest::Reporter* reporter) {
317 // these should not assert in the debug build
318 // bug was introduced in rev. 3209
319 {
320 SkAAClip clip;
321 SkRect r;
322 r.fLeft = SkFloatToScalar(129.892181);
323 r.fTop = SkFloatToScalar(10.3999996);
324 r.fRight = SkFloatToScalar(130.892181);
325 r.fBottom = SkFloatToScalar(20.3999996);
326 clip.setRect(r, true);
327 }
328 }
329
TestAAClip(skiatest::Reporter * reporter)330 static void TestAAClip(skiatest::Reporter* reporter) {
331 test_empty(reporter);
332 test_path_bounds(reporter);
333 test_irect(reporter);
334 test_rgn(reporter);
335 test_path_with_hole(reporter);
336 test_regressions(reporter);
337 }
338
339 #include "TestClassDef.h"
340 DEFINE_TESTCLASS("AAClip", AAClipTestClass, TestAAClip)
341