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