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