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