xref: /aosp_15_r20/external/skia/tests/AAClipTest.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
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