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