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