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 "SkRRect.h"
14 #include "Test.h"
15
operator ==(const SkMask & a,const SkMask & b)16 static bool operator==(const SkMask& a, const SkMask& b) {
17 if (a.fFormat != b.fFormat || a.fBounds != b.fBounds) {
18 return false;
19 }
20 if (!a.fImage && !b.fImage) {
21 return true;
22 }
23 if (!a.fImage || !b.fImage) {
24 return false;
25 }
26
27 size_t wbytes = a.fBounds.width();
28 switch (a.fFormat) {
29 case SkMask::kBW_Format:
30 wbytes = (wbytes + 7) >> 3;
31 break;
32 case SkMask::kA8_Format:
33 case SkMask::k3D_Format:
34 break;
35 case SkMask::kLCD16_Format:
36 wbytes <<= 1;
37 break;
38 case SkMask::kARGB32_Format:
39 wbytes <<= 2;
40 break;
41 default:
42 SkDEBUGFAIL("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 = nullptr;
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 SkImageInfo info = SkImageInfo::Make(mask->fBounds.width(),
75 mask->fBounds.height(),
76 kAlpha_8_SkColorType,
77 kPremul_SkAlphaType);
78 SkBitmap bitmap;
79 bitmap.installPixels(info, mask->fImage, mask->fRowBytes);
80
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
rand_rect(SkRandom & rand,int n)92 static SkIRect rand_rect(SkRandom& rand, int n) {
93 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
make_rand_rgn(SkRegion * rgn,SkRandom & rand)100 static void make_rand_rgn(SkRegion* rgn, SkRandom& rand) {
101 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
operator ==(const SkRegion & rgn,const SkAAClip & aaclip)107 static 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
equalsAAClip(const SkRegion & rgn)119 static bool equalsAAClip(const SkRegion& rgn) {
120 SkAAClip aaclip;
121 aaclip.setRegion(rgn);
122 return rgn == aaclip;
123 }
124
setRgnToPath(SkRegion * rgn,const SkPath & path)125 static void setRgnToPath(SkRegion* rgn, const SkPath& path) {
126 SkIRect ir;
127 path.getBounds().round(&ir);
128 rgn->setPath(path, SkRegion(ir));
129 }
130
131 // aaclip.setRegion should create idential masks to the region
test_rgn(skiatest::Reporter * reporter)132 static void test_rgn(skiatest::Reporter* reporter) {
133 SkRandom rand;
134 for (int i = 0; i < 1000; i++) {
135 SkRegion rgn;
136 make_rand_rgn(&rgn, rand);
137 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));
154 }
155 }
156
157 static const SkRegion::Op gRgnOps[] = {
158 SkRegion::kDifference_Op,
159 SkRegion::kIntersect_Op,
160 SkRegion::kUnion_Op,
161 SkRegion::kXOR_Op,
162 SkRegion::kReverseDifference_Op,
163 SkRegion::kReplace_Op
164 };
165
166 static const char* gRgnOpNames[] = {
167 "DIFF", "INTERSECT", "UNION", "XOR", "REVERSE_DIFF", "REPLACE"
168 };
169
imoveTo(SkPath & path,int x,int y)170 static void imoveTo(SkPath& path, int x, int y) {
171 path.moveTo(SkIntToScalar(x), SkIntToScalar(y));
172 }
173
icubicTo(SkPath & path,int x0,int y0,int x1,int y1,int x2,int y2)174 static 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
test_path_bounds(skiatest::Reporter * reporter)180 static void test_path_bounds(skiatest::Reporter* reporter) {
181 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, nullptr, 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();
199 imoveTo(path, 0, 20);
200 icubicTo(path, 40, 40, 40, 0, 0, 20);
201 REPORTER_ASSERT(reporter, sheight == path.getBounds().height());
202 clip.setPath(path, nullptr, true);
203 REPORTER_ASSERT(reporter, teardrop_height == clip.getBounds().height());
204 }
205
test_empty(skiatest::Reporter * reporter)206 static 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 clip0.copyToMask(&mask);
232 REPORTER_ASSERT(reporter, nullptr == 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 ERRORF(reporter, "%s %s "
266 "[%d %d %d %d] %s [%d %d %d %d] = BW:[%d %d %d %d] AA:[%d %d %d %d]\n",
267 nonEmptyAA == nonEmptyBW ? "true" : "false",
268 clip2.getBounds() == rgn2.getBounds() ? "true" : "false",
269 r0.fLeft, r0.fTop, r0.right(), r0.bottom(),
270 gRgnOpNames[j],
271 r1.fLeft, r1.fTop, r1.right(), r1.bottom(),
272 rgn2.getBounds().fLeft, rgn2.getBounds().fTop,
273 rgn2.getBounds().right(), rgn2.getBounds().bottom(),
274 clip2.getBounds().fLeft, clip2.getBounds().fTop,
275 clip2.getBounds().right(), clip2.getBounds().bottom());
276 }
277
278 SkMask maskBW, maskAA;
279 copyToMask(rgn2, &maskBW);
280 clip2.copyToMask(&maskAA);
281 SkAutoMaskFreeImage freeBW(maskBW.fImage);
282 SkAutoMaskFreeImage freeAA(maskAA.fImage);
283 REPORTER_ASSERT(reporter, maskBW == maskAA);
284 }
285 }
286 }
287
test_path_with_hole(skiatest::Reporter * reporter)288 static 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, nullptr, 1 == i);
312
313 SkMask mask;
314 clip.copyToMask(&mask);
315 SkAutoMaskFreeImage freeM(mask.fImage);
316
317 REPORTER_ASSERT(reporter, expected == mask);
318 }
319 }
320
test_really_a_rect(skiatest::Reporter * reporter)321 static void test_really_a_rect(skiatest::Reporter* reporter) {
322 SkRRect rrect;
323 rrect.setRectXY(SkRect::MakeWH(100, 100), 5, 5);
324
325 SkPath path;
326 path.addRRect(rrect);
327
328 SkAAClip clip;
329 clip.setPath(path);
330
331 REPORTER_ASSERT(reporter, clip.getBounds() == SkIRect::MakeWH(100, 100));
332 REPORTER_ASSERT(reporter, !clip.isRect());
333
334 // This rect should intersect the clip, but slice-out all of the "soft" parts,
335 // leaving just a rect.
336 const SkIRect ir = SkIRect::MakeLTRB(10, -10, 50, 90);
337
338 clip.op(ir, SkRegion::kIntersect_Op);
339
340 REPORTER_ASSERT(reporter, clip.getBounds() == SkIRect::MakeLTRB(10, 0, 50, 90));
341 // the clip recognized that that it is just a rect!
342 REPORTER_ASSERT(reporter, clip.isRect());
343 }
344
345 #include "SkRasterClip.h"
346
copyToMask(const SkRasterClip & rc,SkMask * mask)347 static void copyToMask(const SkRasterClip& rc, SkMask* mask) {
348 if (rc.isAA()) {
349 rc.aaRgn().copyToMask(mask);
350 } else {
351 copyToMask(rc.bwRgn(), mask);
352 }
353 }
354
operator ==(const SkRasterClip & a,const SkRasterClip & b)355 static bool operator==(const SkRasterClip& a, const SkRasterClip& b) {
356 if (a.isEmpty()) {
357 return b.isEmpty();
358 }
359 if (b.isEmpty()) {
360 return false;
361 }
362
363 SkMask ma, mb;
364 copyToMask(a, &ma);
365 copyToMask(b, &mb);
366 SkAutoMaskFreeImage aCleanUp(ma.fImage);
367 SkAutoMaskFreeImage bCleanUp(mb.fImage);
368
369 return ma == mb;
370 }
371
did_dx_affect(skiatest::Reporter * reporter,const SkScalar dx[],size_t count,bool changed)372 static void did_dx_affect(skiatest::Reporter* reporter, const SkScalar dx[],
373 size_t count, bool changed) {
374 const SkIRect baseBounds = SkIRect::MakeXYWH(0, 0, 10, 10);
375 SkIRect ir = { 0, 0, 10, 10 };
376
377 for (size_t i = 0; i < count; ++i) {
378 SkRect r;
379 r.set(ir);
380
381 SkRasterClip rc0(ir);
382 SkRasterClip rc1(ir);
383 SkRasterClip rc2(ir);
384
385 rc0.op(r, baseBounds, SkRegion::kIntersect_Op, false);
386 r.offset(dx[i], 0);
387 rc1.op(r, baseBounds, SkRegion::kIntersect_Op, true);
388 r.offset(-2*dx[i], 0);
389 rc2.op(r, baseBounds, SkRegion::kIntersect_Op, true);
390
391 REPORTER_ASSERT(reporter, changed != (rc0 == rc1));
392 REPORTER_ASSERT(reporter, changed != (rc0 == rc2));
393 }
394 }
395
test_nearly_integral(skiatest::Reporter * reporter)396 static void test_nearly_integral(skiatest::Reporter* reporter) {
397 // All of these should generate equivalent rasterclips
398
399 static const SkScalar gSafeX[] = {
400 0, SK_Scalar1/1000, SK_Scalar1/100, SK_Scalar1/10,
401 };
402 did_dx_affect(reporter, gSafeX, SK_ARRAY_COUNT(gSafeX), false);
403
404 static const SkScalar gUnsafeX[] = {
405 SK_Scalar1/4, SK_Scalar1/3,
406 };
407 did_dx_affect(reporter, gUnsafeX, SK_ARRAY_COUNT(gUnsafeX), true);
408 }
409
test_regressions()410 static void test_regressions() {
411 // these should not assert in the debug build
412 // bug was introduced in rev. 3209
413 {
414 SkAAClip clip;
415 SkRect r;
416 r.fLeft = 129.892181f;
417 r.fTop = 10.3999996f;
418 r.fRight = 130.892181f;
419 r.fBottom = 20.3999996f;
420 clip.setRect(r, true);
421 }
422 }
423
424 // Building aaclip meant aa-scan-convert a path into a huge clip.
425 // the old algorithm sized the supersampler to the size of the clip, which overflowed
426 // its internal 16bit coordinates. The fix was to intersect the clip+path_bounds before
427 // sizing the supersampler.
428 //
429 // Before the fix, the following code would assert in debug builds.
430 //
test_crbug_422693(skiatest::Reporter * reporter)431 static void test_crbug_422693(skiatest::Reporter* reporter) {
432 SkRasterClip rc(SkIRect::MakeLTRB(-25000, -25000, 25000, 25000));
433 SkPath path;
434 path.addCircle(50, 50, 50);
435 rc.op(path, rc.getBounds(), SkRegion::kIntersect_Op, true);
436 }
437
DEF_TEST(AAClip,reporter)438 DEF_TEST(AAClip, reporter) {
439 test_empty(reporter);
440 test_path_bounds(reporter);
441 test_irect(reporter);
442 test_rgn(reporter);
443 test_path_with_hole(reporter);
444 test_regressions();
445 test_nearly_integral(reporter);
446 test_really_a_rect(reporter);
447 test_crbug_422693(reporter);
448 }
449