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