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