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