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 
rand_rect(SkRandom & rand,int n)104 static SkIRect rand_rect(SkRandom& rand, int n) {
105     int x = rand.nextS() % n;
106     int y = rand.nextS() % n;
107     int w = rand.nextU() % n;
108     int h = rand.nextU() % n;
109     return SkIRect::MakeXYWH(x, y, w, h);
110 }
111 
make_rand_rgn(SkRegion * rgn,SkRandom & rand)112 static void make_rand_rgn(SkRegion* rgn, SkRandom& rand) {
113     int count = rand.nextU() % 20;
114     for (int i = 0; i < count; ++i) {
115         rgn->op(rand_rect(rand, 100), SkRegion::kXOR_Op);
116     }
117 }
118 
operator ==(const SkRegion & rgn,const SkAAClip & aaclip)119 static bool operator==(const SkRegion& rgn, const SkAAClip& aaclip) {
120     SkMask mask0, mask1;
121 
122     copyToMask(rgn, &mask0);
123     aaclip.copyToMask(&mask1);
124     bool eq = (mask0 == mask1);
125 
126     SkMask::FreeImage(mask0.fImage);
127     SkMask::FreeImage(mask1.fImage);
128     return eq;
129 }
130 
equalsAAClip(const SkRegion & rgn)131 static bool equalsAAClip(const SkRegion& rgn) {
132     SkAAClip aaclip;
133     aaclip.setRegion(rgn);
134     return rgn == aaclip;
135 }
136 
setRgnToPath(SkRegion * rgn,const SkPath & path)137 static void setRgnToPath(SkRegion* rgn, const SkPath& path) {
138     SkIRect ir;
139     path.getBounds().round(&ir);
140     rgn->setPath(path, SkRegion(ir));
141 }
142 
143 // aaclip.setRegion should create idential masks to the region
test_rgn(skiatest::Reporter * reporter)144 static void test_rgn(skiatest::Reporter* reporter) {
145     SkRandom rand;
146     for (int i = 0; i < 1000; i++) {
147         SkRegion rgn;
148         make_rand_rgn(&rgn, rand);
149         REPORTER_ASSERT(reporter, equalsAAClip(rgn));
150     }
151 
152     {
153         SkRegion rgn;
154         SkPath path;
155         path.addCircle(0, 0, SkIntToScalar(30));
156         setRgnToPath(&rgn, path);
157         REPORTER_ASSERT(reporter, equalsAAClip(rgn));
158 
159         path.reset();
160         path.moveTo(0, 0);
161         path.lineTo(SkIntToScalar(100), 0);
162         path.lineTo(SkIntToScalar(100 - 20), SkIntToScalar(20));
163         path.lineTo(SkIntToScalar(20), SkIntToScalar(20));
164         setRgnToPath(&rgn, path);
165         REPORTER_ASSERT(reporter, equalsAAClip(rgn));
166     }
167 }
168 
169 static const SkRegion::Op gRgnOps[] = {
170     SkRegion::kDifference_Op,
171     SkRegion::kIntersect_Op,
172     SkRegion::kUnion_Op,
173     SkRegion::kXOR_Op,
174     SkRegion::kReverseDifference_Op,
175     SkRegion::kReplace_Op
176 };
177 
178 static const char* gRgnOpNames[] = {
179     "DIFF", "INTERSECT", "UNION", "XOR", "REVERSE_DIFF", "REPLACE"
180 };
181 
imoveTo(SkPath & path,int x,int y)182 static void imoveTo(SkPath& path, int x, int y) {
183     path.moveTo(SkIntToScalar(x), SkIntToScalar(y));
184 }
185 
icubicTo(SkPath & path,int x0,int y0,int x1,int y1,int x2,int y2)186 static void icubicTo(SkPath& path, int x0, int y0, int x1, int y1, int x2, int y2) {
187     path.cubicTo(SkIntToScalar(x0), SkIntToScalar(y0),
188                  SkIntToScalar(x1), SkIntToScalar(y1),
189                  SkIntToScalar(x2), SkIntToScalar(y2));
190 }
191 
test_path_bounds(skiatest::Reporter * reporter)192 static void test_path_bounds(skiatest::Reporter* reporter) {
193     SkPath path;
194     SkAAClip clip;
195     const int height = 40;
196     const SkScalar sheight = SkIntToScalar(height);
197 
198     path.addOval(SkRect::MakeWH(sheight, sheight));
199     REPORTER_ASSERT(reporter, sheight == path.getBounds().height());
200     clip.setPath(path, nullptr, true);
201     REPORTER_ASSERT(reporter, height == clip.getBounds().height());
202 
203     // this is the trimmed height of this cubic (with aa). The critical thing
204     // for this test is that it is less than height, which represents just
205     // the bounds of the path's control-points.
206     //
207     // This used to fail until we tracked the MinY in the BuilderBlitter.
208     //
209     const int teardrop_height = 12;
210     path.reset();
211     imoveTo(path, 0, 20);
212     icubicTo(path, 40, 40, 40, 0, 0, 20);
213     REPORTER_ASSERT(reporter, sheight == path.getBounds().height());
214     clip.setPath(path, nullptr, true);
215     REPORTER_ASSERT(reporter, teardrop_height == clip.getBounds().height());
216 }
217 
test_empty(skiatest::Reporter * reporter)218 static void test_empty(skiatest::Reporter* reporter) {
219     SkAAClip clip0, clip1;
220 
221     REPORTER_ASSERT(reporter, clip0.isEmpty());
222     REPORTER_ASSERT(reporter, clip0.getBounds().isEmpty());
223     REPORTER_ASSERT(reporter, clip1 == clip0);
224 
225     clip0.translate(10, 10);    // should have no effect on empty
226     REPORTER_ASSERT(reporter, clip0.isEmpty());
227     REPORTER_ASSERT(reporter, clip0.getBounds().isEmpty());
228     REPORTER_ASSERT(reporter, clip1 == clip0);
229 
230     SkIRect r = { 10, 10, 40, 50 };
231     clip0.setRect(r);
232     REPORTER_ASSERT(reporter, !clip0.isEmpty());
233     REPORTER_ASSERT(reporter, !clip0.getBounds().isEmpty());
234     REPORTER_ASSERT(reporter, clip0 != clip1);
235     REPORTER_ASSERT(reporter, clip0.getBounds() == r);
236 
237     clip0.setEmpty();
238     REPORTER_ASSERT(reporter, clip0.isEmpty());
239     REPORTER_ASSERT(reporter, clip0.getBounds().isEmpty());
240     REPORTER_ASSERT(reporter, clip1 == clip0);
241 
242     SkMask mask;
243     clip0.copyToMask(&mask);
244     REPORTER_ASSERT(reporter, nullptr == mask.fImage);
245     REPORTER_ASSERT(reporter, mask.fBounds.isEmpty());
246 }
247 
rand_irect(SkIRect * r,int N,SkRandom & rand)248 static void rand_irect(SkIRect* r, int N, SkRandom& rand) {
249     r->setXYWH(0, 0, rand.nextU() % N, rand.nextU() % N);
250     int dx = rand.nextU() % (2*N);
251     int dy = rand.nextU() % (2*N);
252     // use int dx,dy to make the subtract be signed
253     r->offset(N - dx, N - dy);
254 }
255 
test_irect(skiatest::Reporter * reporter)256 static void test_irect(skiatest::Reporter* reporter) {
257     SkRandom rand;
258 
259     for (int i = 0; i < 10000; i++) {
260         SkAAClip clip0, clip1;
261         SkRegion rgn0, rgn1;
262         SkIRect r0, r1;
263 
264         rand_irect(&r0, 10, rand);
265         rand_irect(&r1, 10, rand);
266         clip0.setRect(r0);
267         clip1.setRect(r1);
268         rgn0.setRect(r0);
269         rgn1.setRect(r1);
270         for (size_t j = 0; j < SK_ARRAY_COUNT(gRgnOps); ++j) {
271             SkRegion::Op op = gRgnOps[j];
272             SkAAClip clip2;
273             SkRegion rgn2;
274             bool nonEmptyAA = clip2.op(clip0, clip1, op);
275             bool nonEmptyBW = rgn2.op(rgn0, rgn1, op);
276             if (nonEmptyAA != nonEmptyBW || clip2.getBounds() != rgn2.getBounds()) {
277                 ERRORF(reporter, "%s %s "
278                        "[%d %d %d %d] %s [%d %d %d %d] = BW:[%d %d %d %d] AA:[%d %d %d %d]\n",
279                        nonEmptyAA == nonEmptyBW ? "true" : "false",
280                        clip2.getBounds() == rgn2.getBounds() ? "true" : "false",
281                        r0.fLeft, r0.fTop, r0.right(), r0.bottom(),
282                        gRgnOpNames[j],
283                        r1.fLeft, r1.fTop, r1.right(), r1.bottom(),
284                        rgn2.getBounds().fLeft, rgn2.getBounds().fTop,
285                        rgn2.getBounds().right(), rgn2.getBounds().bottom(),
286                        clip2.getBounds().fLeft, clip2.getBounds().fTop,
287                        clip2.getBounds().right(), clip2.getBounds().bottom());
288             }
289 
290             SkMask maskBW, maskAA;
291             copyToMask(rgn2, &maskBW);
292             clip2.copyToMask(&maskAA);
293             SkAutoMaskFreeImage freeBW(maskBW.fImage);
294             SkAutoMaskFreeImage freeAA(maskAA.fImage);
295             REPORTER_ASSERT(reporter, maskBW == maskAA);
296         }
297     }
298 }
299 
test_path_with_hole(skiatest::Reporter * reporter)300 static void test_path_with_hole(skiatest::Reporter* reporter) {
301     static const uint8_t gExpectedImage[] = {
302         0xFF, 0xFF, 0xFF, 0xFF,
303         0xFF, 0xFF, 0xFF, 0xFF,
304         0x00, 0x00, 0x00, 0x00,
305         0x00, 0x00, 0x00, 0x00,
306         0xFF, 0xFF, 0xFF, 0xFF,
307         0xFF, 0xFF, 0xFF, 0xFF,
308     };
309     SkMask expected;
310     expected.fBounds.setWH(4, 6);
311     expected.fRowBytes = 4;
312     expected.fFormat = SkMask::kA8_Format;
313     expected.fImage = (uint8_t*)gExpectedImage;
314 
315     SkPath path;
316     path.addRect(SkRect::MakeXYWH(0, 0,
317                                   SkIntToScalar(4), SkIntToScalar(2)));
318     path.addRect(SkRect::MakeXYWH(0, SkIntToScalar(4),
319                                   SkIntToScalar(4), SkIntToScalar(2)));
320 
321     for (int i = 0; i < 2; ++i) {
322         SkAAClip clip;
323         clip.setPath(path, nullptr, 1 == i);
324 
325         SkMask mask;
326         clip.copyToMask(&mask);
327         SkAutoMaskFreeImage freeM(mask.fImage);
328 
329         REPORTER_ASSERT(reporter, expected == mask);
330     }
331 }
332 
test_really_a_rect(skiatest::Reporter * reporter)333 static void test_really_a_rect(skiatest::Reporter* reporter) {
334     SkRRect rrect;
335     rrect.setRectXY(SkRect::MakeWH(100, 100), 5, 5);
336 
337     SkPath path;
338     path.addRRect(rrect);
339 
340     SkAAClip clip;
341     clip.setPath(path);
342 
343     REPORTER_ASSERT(reporter, clip.getBounds() == SkIRect::MakeWH(100, 100));
344     REPORTER_ASSERT(reporter, !clip.isRect());
345 
346     // This rect should intersect the clip, but slice-out all of the "soft" parts,
347     // leaving just a rect.
348     const SkIRect ir = SkIRect::MakeLTRB(10, -10, 50, 90);
349 
350     clip.op(ir, SkRegion::kIntersect_Op);
351 
352     REPORTER_ASSERT(reporter, clip.getBounds() == SkIRect::MakeLTRB(10, 0, 50, 90));
353     // the clip recognized that that it is just a rect!
354     REPORTER_ASSERT(reporter, clip.isRect());
355 }
356 
did_dx_affect(skiatest::Reporter * reporter,const SkScalar dx[],size_t count,bool changed)357 static void did_dx_affect(skiatest::Reporter* reporter, const SkScalar dx[],
358                           size_t count, bool changed) {
359     const SkIRect baseBounds = SkIRect::MakeXYWH(0, 0, 10, 10);
360     SkIRect ir = { 0, 0, 10, 10 };
361 
362     for (size_t i = 0; i < count; ++i) {
363         SkRect r;
364         r.set(ir);
365 
366         SkRasterClip rc0(ir);
367         SkRasterClip rc1(ir);
368         SkRasterClip rc2(ir);
369 
370         rc0.op(r, SkMatrix::I(), baseBounds, SkRegion::kIntersect_Op, false);
371         r.offset(dx[i], 0);
372         rc1.op(r, SkMatrix::I(), baseBounds, SkRegion::kIntersect_Op, true);
373         r.offset(-2*dx[i], 0);
374         rc2.op(r, SkMatrix::I(), baseBounds, SkRegion::kIntersect_Op, true);
375 
376         REPORTER_ASSERT(reporter, changed != (rc0 == rc1));
377         REPORTER_ASSERT(reporter, changed != (rc0 == rc2));
378     }
379 }
380 
test_nearly_integral(skiatest::Reporter * reporter)381 static void test_nearly_integral(skiatest::Reporter* reporter) {
382     // All of these should generate equivalent rasterclips
383 
384     static const SkScalar gSafeX[] = {
385         0, SK_Scalar1/1000, SK_Scalar1/100, SK_Scalar1/10,
386     };
387     did_dx_affect(reporter, gSafeX, SK_ARRAY_COUNT(gSafeX), false);
388 
389     static const SkScalar gUnsafeX[] = {
390         SK_Scalar1/4, SK_Scalar1/3,
391     };
392     did_dx_affect(reporter, gUnsafeX, SK_ARRAY_COUNT(gUnsafeX), true);
393 }
394 
test_regressions()395 static void test_regressions() {
396     // these should not assert in the debug build
397     // bug was introduced in rev. 3209
398     {
399         SkAAClip clip;
400         SkRect r;
401         r.fLeft = 129.892181f;
402         r.fTop = 10.3999996f;
403         r.fRight = 130.892181f;
404         r.fBottom = 20.3999996f;
405         clip.setRect(r, true);
406     }
407 }
408 
409 // Building aaclip meant aa-scan-convert a path into a huge clip.
410 // the old algorithm sized the supersampler to the size of the clip, which overflowed
411 // its internal 16bit coordinates. The fix was to intersect the clip+path_bounds before
412 // sizing the supersampler.
413 //
414 // Before the fix, the following code would assert in debug builds.
415 //
test_crbug_422693(skiatest::Reporter * reporter)416 static void test_crbug_422693(skiatest::Reporter* reporter) {
417     SkRasterClip rc(SkIRect::MakeLTRB(-25000, -25000, 25000, 25000));
418     SkPath path;
419     path.addCircle(50, 50, 50);
420     rc.op(path, SkMatrix::I(), rc.getBounds(), SkRegion::kIntersect_Op, true);
421 }
422 
test_huge(skiatest::Reporter * reporter)423 static void test_huge(skiatest::Reporter* reporter) {
424     SkAAClip clip;
425     int big = 0x70000000;
426     SkIRect r = { -big, -big, big, big };
427     SkASSERT(r.width() < 0 && r.height() < 0);
428 
429     clip.setRect(r);
430 }
431 
DEF_TEST(AAClip,reporter)432 DEF_TEST(AAClip, reporter) {
433     test_empty(reporter);
434     test_path_bounds(reporter);
435     test_irect(reporter);
436     test_rgn(reporter);
437     test_path_with_hole(reporter);
438     test_regressions();
439     test_nearly_integral(reporter);
440     test_really_a_rect(reporter);
441     test_crbug_422693(reporter);
442     test_huge(reporter);
443 }
444