• 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/SkTemplates.h"
23 #include "include/private/base/SkMalloc.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 <string>
34 
operator ==(const SkMask & a,const SkMask & b)35 static bool operator==(const SkMask& a, const SkMask& b) {
36     if (a.fFormat != b.fFormat || a.fBounds != b.fBounds) {
37         return false;
38     }
39     if (!a.fImage && !b.fImage) {
40         return true;
41     }
42     if (!a.fImage || !b.fImage) {
43         return false;
44     }
45 
46     size_t wbytes = a.fBounds.width();
47     switch (a.fFormat) {
48         case SkMask::kBW_Format:
49             wbytes = (wbytes + 7) >> 3;
50             break;
51         case SkMask::kA8_Format:
52         case SkMask::k3D_Format:
53             break;
54         case SkMask::kLCD16_Format:
55             wbytes <<= 1;
56             break;
57         case SkMask::kARGB32_Format:
58             wbytes <<= 2;
59             break;
60         default:
61             SkDEBUGFAIL("unknown mask format");
62             return false;
63     }
64 
65     const int h = a.fBounds.height();
66     const char* aptr = (const char*)a.fImage;
67     const char* bptr = (const char*)b.fImage;
68     for (int y = 0; y < h; ++y) {
69         if (0 != memcmp(aptr, bptr, wbytes)) {
70             return false;
71         }
72         aptr += wbytes;
73         bptr += wbytes;
74     }
75     return true;
76 }
77 
copyToMask(const SkRegion & rgn,SkMask * mask)78 static void copyToMask(const SkRegion& rgn, SkMask* mask) {
79     mask->fFormat = SkMask::kA8_Format;
80 
81     if (rgn.isEmpty()) {
82         mask->fBounds.setEmpty();
83         mask->fRowBytes = 0;
84         mask->fImage = nullptr;
85         return;
86     }
87 
88     mask->fBounds = rgn.getBounds();
89     mask->fRowBytes = mask->fBounds.width();
90     mask->fImage = SkMask::AllocImage(mask->computeImageSize());
91     sk_bzero(mask->fImage, mask->computeImageSize());
92 
93     SkImageInfo info = SkImageInfo::Make(mask->fBounds.width(),
94                                          mask->fBounds.height(),
95                                          kAlpha_8_SkColorType,
96                                          kPremul_SkAlphaType);
97     SkBitmap bitmap;
98     bitmap.installPixels(info, mask->fImage, mask->fRowBytes);
99 
100     // canvas expects its coordinate system to always be 0,0 in the top/left
101     // so we translate the rgn to match that before drawing into the mask.
102     //
103     SkRegion tmpRgn(rgn);
104     tmpRgn.translate(-rgn.getBounds().fLeft, -rgn.getBounds().fTop);
105 
106     SkCanvas canvas(bitmap);
107     canvas.clipRegion(tmpRgn);
108     canvas.drawColor(SK_ColorBLACK);
109 }
110 
copyToMask(const SkRasterClip & rc,SkMask * mask)111 static void copyToMask(const SkRasterClip& rc, SkMask* mask) {
112     if (rc.isBW()) {
113         copyToMask(rc.bwRgn(), mask);
114     } else {
115         rc.aaRgn().copyToMask(mask);
116     }
117 }
118 
operator ==(const SkRasterClip & a,const SkRasterClip & b)119 static bool operator==(const SkRasterClip& a, const SkRasterClip& b) {
120     if (a.isEmpty() && b.isEmpty()) {
121         return true;
122     } else if (a.isEmpty() != b.isEmpty() || a.isBW() != b.isBW() || a.isRect() != b.isRect()) {
123         return false;
124     }
125 
126     SkMask mask0, mask1;
127     copyToMask(a, &mask0);
128     copyToMask(b, &mask1);
129     SkAutoMaskFreeImage free0(mask0.fImage);
130     SkAutoMaskFreeImage free1(mask1.fImage);
131     return mask0 == mask1;
132 }
133 
rand_rect(SkRandom & rand,int n)134 static SkIRect rand_rect(SkRandom& rand, int n) {
135     int x = rand.nextS() % n;
136     int y = rand.nextS() % n;
137     int w = rand.nextU() % n;
138     int h = rand.nextU() % n;
139     return SkIRect::MakeXYWH(x, y, w, h);
140 }
141 
make_rand_rgn(SkRegion * rgn,SkRandom & rand)142 static void make_rand_rgn(SkRegion* rgn, SkRandom& rand) {
143     int count = rand.nextU() % 20;
144     for (int i = 0; i < count; ++i) {
145         rgn->op(rand_rect(rand, 100), SkRegion::kXOR_Op);
146     }
147 }
148 
operator ==(const SkRegion & rgn,const SkAAClip & aaclip)149 static bool operator==(const SkRegion& rgn, const SkAAClip& aaclip) {
150     SkMask mask0, mask1;
151 
152     copyToMask(rgn, &mask0);
153     aaclip.copyToMask(&mask1);
154     SkAutoMaskFreeImage free0(mask0.fImage);
155     SkAutoMaskFreeImage free1(mask1.fImage);
156     return mask0 == mask1;
157 }
158 
equalsAAClip(const SkRegion & rgn)159 static bool equalsAAClip(const SkRegion& rgn) {
160     SkAAClip aaclip;
161     aaclip.setRegion(rgn);
162     return rgn == aaclip;
163 }
164 
setRgnToPath(SkRegion * rgn,const SkPath & path)165 static void setRgnToPath(SkRegion* rgn, const SkPath& path) {
166     SkIRect ir;
167     path.getBounds().round(&ir);
168     rgn->setPath(path, SkRegion(ir));
169 }
170 
171 // aaclip.setRegion should create idential masks to the region
test_rgn(skiatest::Reporter * reporter)172 static void test_rgn(skiatest::Reporter* 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 
test_path_bounds(skiatest::Reporter * reporter)207 static void test_path_bounds(skiatest::Reporter* 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 
test_empty(skiatest::Reporter * reporter)233 static void test_empty(skiatest::Reporter* 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     SkMask 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 
test_irect(skiatest::Reporter * reporter)267 static void test_irect(skiatest::Reporter* 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             SkMask maskBW, maskAA;
301             copyToMask(rgn2, &maskBW);
302             clip2.copyToMask(&maskAA);
303             SkAutoMaskFreeImage freeBW(maskBW.fImage);
304             SkAutoMaskFreeImage freeAA(maskAA.fImage);
305             REPORTER_ASSERT(reporter, maskBW == maskAA);
306         }
307     }
308 }
309 
test_path_with_hole(skiatest::Reporter * reporter)310 static void test_path_with_hole(skiatest::Reporter* 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;
320     expected.fBounds.setWH(4, 6);
321     expected.fRowBytes = 4;
322     expected.fFormat = SkMask::kA8_Format;
323     expected.fImage = (uint8_t*)gExpectedImage;
324 
325     SkPath path;
326     path.addRect(SkRect::MakeXYWH(0, 0,
327                                   SkIntToScalar(4), SkIntToScalar(2)));
328     path.addRect(SkRect::MakeXYWH(0, SkIntToScalar(4),
329                                   SkIntToScalar(4), SkIntToScalar(2)));
330 
331     for (int i = 0; i < 2; ++i) {
332         SkAAClip clip;
333         clip.setPath(path, path.getBounds().roundOut(), 1 == i);
334 
335         SkMask mask;
336         clip.copyToMask(&mask);
337         SkAutoMaskFreeImage freeM(mask.fImage);
338 
339         REPORTER_ASSERT(reporter, expected == mask);
340     }
341 }
342 
test_really_a_rect(skiatest::Reporter * reporter)343 static void test_really_a_rect(skiatest::Reporter* reporter) {
344     SkRRect rrect;
345     rrect.setRectXY(SkRect::MakeWH(100, 100), 5, 5);
346 
347     SkPath path;
348     path.addRRect(rrect);
349 
350     SkAAClip clip;
351     clip.setPath(path, path.getBounds().roundOut(), true);
352 
353     REPORTER_ASSERT(reporter, clip.getBounds() == SkIRect::MakeWH(100, 100));
354     REPORTER_ASSERT(reporter, !clip.isRect());
355 
356     // This rect should intersect the clip, but slice-out all of the "soft" parts,
357     // leaving just a rect.
358     const SkIRect ir = SkIRect::MakeLTRB(10, -10, 50, 90);
359 
360     clip.op(ir, SkClipOp::kIntersect);
361 
362     REPORTER_ASSERT(reporter, clip.getBounds() == SkIRect::MakeLTRB(10, 0, 50, 90));
363     // the clip recognized that that it is just a rect!
364     REPORTER_ASSERT(reporter, clip.isRect());
365 }
366 
did_dx_affect(skiatest::Reporter * reporter,const SkScalar dx[],size_t count,bool changed)367 static void did_dx_affect(skiatest::Reporter* reporter, const SkScalar dx[],
368                           size_t count, bool changed) {
369     SkIRect ir = { 0, 0, 10, 10 };
370 
371     for (size_t i = 0; i < count; ++i) {
372         SkRect r;
373         r.set(ir);
374 
375         SkRasterClip rc0(ir);
376         SkRasterClip rc1(ir);
377         SkRasterClip rc2(ir);
378 
379         rc0.op(r, SkMatrix::I(), SkClipOp::kIntersect, false);
380         r.offset(dx[i], 0);
381         rc1.op(r, SkMatrix::I(), SkClipOp::kIntersect, true);
382         r.offset(-2*dx[i], 0);
383         rc2.op(r, SkMatrix::I(), SkClipOp::kIntersect, true);
384 
385         REPORTER_ASSERT(reporter, changed != (rc0 == rc1));
386         REPORTER_ASSERT(reporter, changed != (rc0 == rc2));
387     }
388 }
389 
test_nearly_integral(skiatest::Reporter * reporter)390 static void test_nearly_integral(skiatest::Reporter* reporter) {
391     // All of these should generate equivalent rasterclips
392 
393     static const SkScalar gSafeX[] = {
394         0, SK_Scalar1/1000, SK_Scalar1/100, SK_Scalar1/10,
395     };
396     did_dx_affect(reporter, gSafeX, std::size(gSafeX), false);
397 
398     static const SkScalar gUnsafeX[] = {
399         SK_Scalar1/4, SK_Scalar1/3,
400     };
401     did_dx_affect(reporter, gUnsafeX, std::size(gUnsafeX), true);
402 }
403 
test_regressions()404 static void test_regressions() {
405     // these should not assert in the debug build
406     // bug was introduced in rev. 3209
407     {
408         SkAAClip clip;
409         SkRect r;
410         r.fLeft = 129.892181f;
411         r.fTop = 10.3999996f;
412         r.fRight = 130.892181f;
413         r.fBottom = 20.3999996f;
414         clip.setPath(SkPath::Rect(r), r.roundOut(), true);
415     }
416 }
417 
418 // Building aaclip meant aa-scan-convert a path into a huge clip.
419 // the old algorithm sized the supersampler to the size of the clip, which overflowed
420 // its internal 16bit coordinates. The fix was to intersect the clip+path_bounds before
421 // sizing the supersampler.
422 //
423 // Before the fix, the following code would assert in debug builds.
424 //
test_crbug_422693(skiatest::Reporter * reporter)425 static void test_crbug_422693(skiatest::Reporter* reporter) {
426     SkRasterClip rc(SkIRect::MakeLTRB(-25000, -25000, 25000, 25000));
427     SkPath path;
428     path.addCircle(50, 50, 50);
429     rc.op(path, SkMatrix::I(), SkClipOp::kIntersect, true);
430 }
431 
test_huge(skiatest::Reporter * reporter)432 static void test_huge(skiatest::Reporter* reporter) {
433     SkAAClip clip;
434     int big = 0x70000000;
435     SkIRect r = { -big, -big, big, big };
436     SkASSERT(r.width() < 0 && r.height() < 0);
437 
438     clip.setRect(r);
439 }
440 
DEF_TEST(AAClip,reporter)441 DEF_TEST(AAClip, reporter) {
442     test_empty(reporter);
443     test_path_bounds(reporter);
444     test_irect(reporter);
445     test_rgn(reporter);
446     test_path_with_hole(reporter);
447     test_regressions();
448     test_nearly_integral(reporter);
449     test_really_a_rect(reporter);
450     test_crbug_422693(reporter);
451     test_huge(reporter);
452 }
453