• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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