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