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 "SkAAClip.h"
9 #include "SkCanvas.h"
10 #include "SkMask.h"
11 #include "SkPath.h"
12 #include "SkRandom.h"
13 #include "Test.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::kARGB32_Format:
38 wbytes <<= 2;
39 break;
40 default:
41 SkDEBUGFAIL("unknown mask format");
42 return false;
43 }
44
45 const int h = a.fBounds.height();
46 const char* aptr = (const char*)a.fImage;
47 const char* bptr = (const char*)b.fImage;
48 for (int y = 0; y < h; ++y) {
49 if (memcmp(aptr, bptr, wbytes)) {
50 return false;
51 }
52 aptr += wbytes;
53 bptr += wbytes;
54 }
55 return true;
56 }
57
copyToMask(const SkRegion & rgn,SkMask * mask)58 static void copyToMask(const SkRegion& rgn, SkMask* mask) {
59 mask->fFormat = SkMask::kA8_Format;
60
61 if (rgn.isEmpty()) {
62 mask->fBounds.setEmpty();
63 mask->fRowBytes = 0;
64 mask->fImage = NULL;
65 return;
66 }
67
68 mask->fBounds = rgn.getBounds();
69 mask->fRowBytes = mask->fBounds.width();
70 mask->fImage = SkMask::AllocImage(mask->computeImageSize());
71 sk_bzero(mask->fImage, mask->computeImageSize());
72
73 SkImageInfo info = SkImageInfo::Make(mask->fBounds.width(),
74 mask->fBounds.height(),
75 kAlpha_8_SkColorType,
76 kPremul_SkAlphaType);
77 SkBitmap bitmap;
78 bitmap.installPixels(info, mask->fImage, mask->fRowBytes);
79
80 // canvas expects its coordinate system to always be 0,0 in the top/left
81 // so we translate the rgn to match that before drawing into the mask.
82 //
83 SkRegion tmpRgn(rgn);
84 tmpRgn.translate(-rgn.getBounds().fLeft, -rgn.getBounds().fTop);
85
86 SkCanvas canvas(bitmap);
87 canvas.clipRegion(tmpRgn);
88 canvas.drawColor(SK_ColorBLACK);
89 }
90
rand_rect(SkRandom & rand,int n)91 static SkIRect rand_rect(SkRandom& rand, int n) {
92 int x = rand.nextS() % n;
93 int y = rand.nextS() % n;
94 int w = rand.nextU() % n;
95 int h = rand.nextU() % n;
96 return SkIRect::MakeXYWH(x, y, w, h);
97 }
98
make_rand_rgn(SkRegion * rgn,SkRandom & rand)99 static void make_rand_rgn(SkRegion* rgn, SkRandom& rand) {
100 int count = rand.nextU() % 20;
101 for (int i = 0; i < count; ++i) {
102 rgn->op(rand_rect(rand, 100), SkRegion::kXOR_Op);
103 }
104 }
105
operator ==(const SkRegion & rgn,const SkAAClip & aaclip)106 static bool operator==(const SkRegion& rgn, const SkAAClip& aaclip) {
107 SkMask mask0, mask1;
108
109 copyToMask(rgn, &mask0);
110 aaclip.copyToMask(&mask1);
111 bool eq = (mask0 == mask1);
112
113 SkMask::FreeImage(mask0.fImage);
114 SkMask::FreeImage(mask1.fImage);
115 return eq;
116 }
117
equalsAAClip(const SkRegion & rgn)118 static bool equalsAAClip(const SkRegion& rgn) {
119 SkAAClip aaclip;
120 aaclip.setRegion(rgn);
121 return rgn == aaclip;
122 }
123
setRgnToPath(SkRegion * rgn,const SkPath & path)124 static void setRgnToPath(SkRegion* rgn, const SkPath& path) {
125 SkIRect ir;
126 path.getBounds().round(&ir);
127 rgn->setPath(path, SkRegion(ir));
128 }
129
130 // aaclip.setRegion should create idential masks to the region
test_rgn(skiatest::Reporter * reporter)131 static void test_rgn(skiatest::Reporter* reporter) {
132 SkRandom rand;
133 for (int i = 0; i < 1000; i++) {
134 SkRegion rgn;
135 make_rand_rgn(&rgn, rand);
136 REPORTER_ASSERT(reporter, equalsAAClip(rgn));
137 }
138
139 {
140 SkRegion rgn;
141 SkPath path;
142 path.addCircle(0, 0, SkIntToScalar(30));
143 setRgnToPath(&rgn, path);
144 REPORTER_ASSERT(reporter, equalsAAClip(rgn));
145
146 path.reset();
147 path.moveTo(0, 0);
148 path.lineTo(SkIntToScalar(100), 0);
149 path.lineTo(SkIntToScalar(100 - 20), SkIntToScalar(20));
150 path.lineTo(SkIntToScalar(20), SkIntToScalar(20));
151 setRgnToPath(&rgn, path);
152 REPORTER_ASSERT(reporter, equalsAAClip(rgn));
153 }
154 }
155
156 static const SkRegion::Op gRgnOps[] = {
157 SkRegion::kDifference_Op,
158 SkRegion::kIntersect_Op,
159 SkRegion::kUnion_Op,
160 SkRegion::kXOR_Op,
161 SkRegion::kReverseDifference_Op,
162 SkRegion::kReplace_Op
163 };
164
165 static const char* gRgnOpNames[] = {
166 "DIFF", "INTERSECT", "UNION", "XOR", "REVERSE_DIFF", "REPLACE"
167 };
168
imoveTo(SkPath & path,int x,int y)169 static void imoveTo(SkPath& path, int x, int y) {
170 path.moveTo(SkIntToScalar(x), SkIntToScalar(y));
171 }
172
icubicTo(SkPath & path,int x0,int y0,int x1,int y1,int x2,int y2)173 static void icubicTo(SkPath& path, int x0, int y0, int x1, int y1, int x2, int y2) {
174 path.cubicTo(SkIntToScalar(x0), SkIntToScalar(y0),
175 SkIntToScalar(x1), SkIntToScalar(y1),
176 SkIntToScalar(x2), SkIntToScalar(y2));
177 }
178
test_path_bounds(skiatest::Reporter * reporter)179 static void test_path_bounds(skiatest::Reporter* reporter) {
180 SkPath path;
181 SkAAClip clip;
182 const int height = 40;
183 const SkScalar sheight = SkIntToScalar(height);
184
185 path.addOval(SkRect::MakeWH(sheight, sheight));
186 REPORTER_ASSERT(reporter, sheight == path.getBounds().height());
187 clip.setPath(path, NULL, true);
188 REPORTER_ASSERT(reporter, height == clip.getBounds().height());
189
190 // this is the trimmed height of this cubic (with aa). The critical thing
191 // for this test is that it is less than height, which represents just
192 // the bounds of the path's control-points.
193 //
194 // This used to fail until we tracked the MinY in the BuilderBlitter.
195 //
196 const int teardrop_height = 12;
197 path.reset();
198 imoveTo(path, 0, 20);
199 icubicTo(path, 40, 40, 40, 0, 0, 20);
200 REPORTER_ASSERT(reporter, sheight == path.getBounds().height());
201 clip.setPath(path, NULL, true);
202 REPORTER_ASSERT(reporter, teardrop_height == clip.getBounds().height());
203 }
204
test_empty(skiatest::Reporter * reporter)205 static void test_empty(skiatest::Reporter* reporter) {
206 SkAAClip clip0, clip1;
207
208 REPORTER_ASSERT(reporter, clip0.isEmpty());
209 REPORTER_ASSERT(reporter, clip0.getBounds().isEmpty());
210 REPORTER_ASSERT(reporter, clip1 == clip0);
211
212 clip0.translate(10, 10); // should have no effect on empty
213 REPORTER_ASSERT(reporter, clip0.isEmpty());
214 REPORTER_ASSERT(reporter, clip0.getBounds().isEmpty());
215 REPORTER_ASSERT(reporter, clip1 == clip0);
216
217 SkIRect r = { 10, 10, 40, 50 };
218 clip0.setRect(r);
219 REPORTER_ASSERT(reporter, !clip0.isEmpty());
220 REPORTER_ASSERT(reporter, !clip0.getBounds().isEmpty());
221 REPORTER_ASSERT(reporter, clip0 != clip1);
222 REPORTER_ASSERT(reporter, clip0.getBounds() == r);
223
224 clip0.setEmpty();
225 REPORTER_ASSERT(reporter, clip0.isEmpty());
226 REPORTER_ASSERT(reporter, clip0.getBounds().isEmpty());
227 REPORTER_ASSERT(reporter, clip1 == clip0);
228
229 SkMask mask;
230 mask.fImage = NULL;
231 clip0.copyToMask(&mask);
232 REPORTER_ASSERT(reporter, NULL == mask.fImage);
233 REPORTER_ASSERT(reporter, mask.fBounds.isEmpty());
234 }
235
rand_irect(SkIRect * r,int N,SkRandom & rand)236 static void rand_irect(SkIRect* r, int N, SkRandom& rand) {
237 r->setXYWH(0, 0, rand.nextU() % N, rand.nextU() % N);
238 int dx = rand.nextU() % (2*N);
239 int dy = rand.nextU() % (2*N);
240 // use int dx,dy to make the subtract be signed
241 r->offset(N - dx, N - dy);
242 }
243
test_irect(skiatest::Reporter * reporter)244 static void test_irect(skiatest::Reporter* reporter) {
245 SkRandom rand;
246
247 for (int i = 0; i < 10000; i++) {
248 SkAAClip clip0, clip1;
249 SkRegion rgn0, rgn1;
250 SkIRect r0, r1;
251
252 rand_irect(&r0, 10, rand);
253 rand_irect(&r1, 10, rand);
254 clip0.setRect(r0);
255 clip1.setRect(r1);
256 rgn0.setRect(r0);
257 rgn1.setRect(r1);
258 for (size_t j = 0; j < SK_ARRAY_COUNT(gRgnOps); ++j) {
259 SkRegion::Op op = gRgnOps[j];
260 SkAAClip clip2;
261 SkRegion rgn2;
262 bool nonEmptyAA = clip2.op(clip0, clip1, op);
263 bool nonEmptyBW = rgn2.op(rgn0, rgn1, op);
264 if (nonEmptyAA != nonEmptyBW || clip2.getBounds() != rgn2.getBounds()) {
265 SkDebugf("[%d %d %d %d] %s [%d %d %d %d] = BW:[%d %d %d %d] AA:[%d %d %d %d]\n",
266 r0.fLeft, r0.fTop, r0.right(), r0.bottom(),
267 gRgnOpNames[j],
268 r1.fLeft, r1.fTop, r1.right(), r1.bottom(),
269 rgn2.getBounds().fLeft, rgn2.getBounds().fTop,
270 rgn2.getBounds().right(), rgn2.getBounds().bottom(),
271 clip2.getBounds().fLeft, clip2.getBounds().fTop,
272 clip2.getBounds().right(), clip2.getBounds().bottom());
273 }
274 REPORTER_ASSERT(reporter, nonEmptyAA == nonEmptyBW);
275 REPORTER_ASSERT(reporter, clip2.getBounds() == rgn2.getBounds());
276
277 SkMask maskBW, maskAA;
278 copyToMask(rgn2, &maskBW);
279 clip2.copyToMask(&maskAA);
280 SkAutoMaskFreeImage freeBW(maskBW.fImage);
281 SkAutoMaskFreeImage freeAA(maskAA.fImage);
282 REPORTER_ASSERT(reporter, maskBW == maskAA);
283 }
284 }
285 }
286
test_path_with_hole(skiatest::Reporter * reporter)287 static void test_path_with_hole(skiatest::Reporter* reporter) {
288 static const uint8_t gExpectedImage[] = {
289 0xFF, 0xFF, 0xFF, 0xFF,
290 0xFF, 0xFF, 0xFF, 0xFF,
291 0x00, 0x00, 0x00, 0x00,
292 0x00, 0x00, 0x00, 0x00,
293 0xFF, 0xFF, 0xFF, 0xFF,
294 0xFF, 0xFF, 0xFF, 0xFF,
295 };
296 SkMask expected;
297 expected.fBounds.set(0, 0, 4, 6);
298 expected.fRowBytes = 4;
299 expected.fFormat = SkMask::kA8_Format;
300 expected.fImage = (uint8_t*)gExpectedImage;
301
302 SkPath path;
303 path.addRect(SkRect::MakeXYWH(0, 0,
304 SkIntToScalar(4), SkIntToScalar(2)));
305 path.addRect(SkRect::MakeXYWH(0, SkIntToScalar(4),
306 SkIntToScalar(4), SkIntToScalar(2)));
307
308 for (int i = 0; i < 2; ++i) {
309 SkAAClip clip;
310 clip.setPath(path, NULL, 1 == i);
311
312 SkMask mask;
313 clip.copyToMask(&mask);
314 SkAutoMaskFreeImage freeM(mask.fImage);
315
316 REPORTER_ASSERT(reporter, expected == mask);
317 }
318 }
319
test_really_a_rect(skiatest::Reporter * reporter)320 static void test_really_a_rect(skiatest::Reporter* reporter) {
321 SkRRect rrect;
322 rrect.setRectXY(SkRect::MakeWH(100, 100), 5, 5);
323
324 SkPath path;
325 path.addRRect(rrect);
326
327 SkAAClip clip;
328 clip.setPath(path);
329
330 REPORTER_ASSERT(reporter, clip.getBounds() == SkIRect::MakeWH(100, 100));
331 REPORTER_ASSERT(reporter, !clip.isRect());
332
333 // This rect should intersect the clip, but slice-out all of the "soft" parts,
334 // leaving just a rect.
335 const SkIRect ir = SkIRect::MakeLTRB(10, -10, 50, 90);
336
337 clip.op(ir, SkRegion::kIntersect_Op);
338
339 REPORTER_ASSERT(reporter, clip.getBounds() == SkIRect::MakeLTRB(10, 0, 50, 90));
340 // the clip recognized that that it is just a rect!
341 REPORTER_ASSERT(reporter, clip.isRect());
342 }
343
344 #include "SkRasterClip.h"
345
copyToMask(const SkRasterClip & rc,SkMask * mask)346 static void copyToMask(const SkRasterClip& rc, SkMask* mask) {
347 if (rc.isAA()) {
348 rc.aaRgn().copyToMask(mask);
349 } else {
350 copyToMask(rc.bwRgn(), mask);
351 }
352 }
353
operator ==(const SkRasterClip & a,const SkRasterClip & b)354 static bool operator==(const SkRasterClip& a, const SkRasterClip& b) {
355 if (a.isEmpty()) {
356 return b.isEmpty();
357 }
358 if (b.isEmpty()) {
359 return false;
360 }
361
362 SkMask ma, mb;
363 copyToMask(a, &ma);
364 copyToMask(b, &mb);
365 SkAutoMaskFreeImage aCleanUp(ma.fImage);
366 SkAutoMaskFreeImage bCleanUp(mb.fImage);
367
368 return ma == mb;
369 }
370
did_dx_affect(skiatest::Reporter * reporter,const SkScalar dx[],size_t count,bool changed)371 static void did_dx_affect(skiatest::Reporter* reporter, const SkScalar dx[],
372 size_t count, bool changed) {
373 const SkISize baseSize = SkISize::Make(10, 10);
374 SkIRect ir = { 0, 0, 10, 10 };
375
376 for (size_t i = 0; i < count; ++i) {
377 SkRect r;
378 r.set(ir);
379
380 SkRasterClip rc0(ir);
381 SkRasterClip rc1(ir);
382 SkRasterClip rc2(ir);
383
384 rc0.op(r, baseSize, SkRegion::kIntersect_Op, false);
385 r.offset(dx[i], 0);
386 rc1.op(r, baseSize, SkRegion::kIntersect_Op, true);
387 r.offset(-2*dx[i], 0);
388 rc2.op(r, baseSize, SkRegion::kIntersect_Op, true);
389
390 REPORTER_ASSERT(reporter, changed != (rc0 == rc1));
391 REPORTER_ASSERT(reporter, changed != (rc0 == rc2));
392 }
393 }
394
test_nearly_integral(skiatest::Reporter * reporter)395 static void test_nearly_integral(skiatest::Reporter* reporter) {
396 // All of these should generate equivalent rasterclips
397
398 static const SkScalar gSafeX[] = {
399 0, SK_Scalar1/1000, SK_Scalar1/100, SK_Scalar1/10,
400 };
401 did_dx_affect(reporter, gSafeX, SK_ARRAY_COUNT(gSafeX), false);
402
403 static const SkScalar gUnsafeX[] = {
404 SK_Scalar1/4, SK_Scalar1/3,
405 };
406 did_dx_affect(reporter, gUnsafeX, SK_ARRAY_COUNT(gUnsafeX), true);
407 }
408
test_regressions()409 static void test_regressions() {
410 // these should not assert in the debug build
411 // bug was introduced in rev. 3209
412 {
413 SkAAClip clip;
414 SkRect r;
415 r.fLeft = 129.892181f;
416 r.fTop = 10.3999996f;
417 r.fRight = 130.892181f;
418 r.fBottom = 20.3999996f;
419 clip.setRect(r, true);
420 }
421 }
422
423 // Building aaclip meant aa-scan-convert a path into a huge clip.
424 // the old algorithm sized the supersampler to the size of the clip, which overflowed
425 // its internal 16bit coordinates. The fix was to intersect the clip+path_bounds before
426 // sizing the supersampler.
427 //
428 // Before the fix, the following code would assert in debug builds.
429 //
test_crbug_422693(skiatest::Reporter * reporter)430 static void test_crbug_422693(skiatest::Reporter* reporter) {
431 SkRasterClip rc(SkIRect::MakeLTRB(-25000, -25000, 25000, 25000));
432 SkPath path;
433 path.addCircle(50, 50, 50);
434 rc.op(path, rc.getBounds().size(), SkRegion::kIntersect_Op, true);
435 }
436
DEF_TEST(AAClip,reporter)437 DEF_TEST(AAClip, reporter) {
438 test_empty(reporter);
439 test_path_bounds(reporter);
440 test_irect(reporter);
441 test_rgn(reporter);
442 test_path_with_hole(reporter);
443 test_regressions();
444 test_nearly_integral(reporter);
445 test_really_a_rect(reporter);
446 test_crbug_422693(reporter);
447 }
448