• Home
  • History
  • Annotate
  • 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