2 * Copyright 2011 Google Inc.
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
15 static bool operator==(const SkMask& a, const SkMask& b) {
16 if (a.fFormat != b.fFormat || a.fBounds != b.fBounds) {
19 if (!a.fImage && !b.fImage) {
22 if (!a.fImage || !b.fImage) {
26 size_t wbytes = a.fBounds.width();
28 case SkMask::kBW_Format:
29 wbytes = (wbytes + 7) >> 3;
31 case SkMask::kA8_Format:
32 case SkMask::k3D_Format:
34 case SkMask::kLCD16_Format:
37 case SkMask::kLCD32_Format:
38 case SkMask::kARGB32_Format:
42 SkDEBUGFAIL("unknown mask format");
46 const int h = a.fBounds.height();
47 const char* aptr = (const char*)a.fImage;
48 const char* bptr = (const char*)b.fImage;
49 for (int y = 0; y < h; ++y) {
50 if (memcmp(aptr, bptr, wbytes)) {
59 static void copyToMask(const SkRegion& rgn, SkMask* mask) {
60 mask->fFormat = SkMask::kA8_Format;
63 mask->fBounds.setEmpty();
69 mask->fBounds = rgn.getBounds();
70 mask->fRowBytes = mask->fBounds.width();
71 mask->fImage = SkMask::AllocImage(mask->computeImageSize());
72 sk_bzero(mask->fImage, mask->computeImageSize());
74 SkImageInfo info = SkImageInfo::Make(mask->fBounds.width(),
75 mask->fBounds.height(),
79 bitmap.installPixels(info, mask->fImage, mask->fRowBytes);
81 // canvas expects its coordinate system to always be 0,0 in the top/left
82 // so we translate the rgn to match that before drawing into the mask.
85 tmpRgn.translate(-rgn.getBounds().fLeft, -rgn.getBounds().fTop);
87 SkCanvas canvas(bitmap);
88 canvas.clipRegion(tmpRgn);
89 canvas.drawColor(SK_ColorBLACK);
92 static SkIRect rand_rect(SkRandom& rand, int n) {
93 int x = rand.nextS() % n;
94 int y = rand.nextS() % n;
95 int w = rand.nextU() % n;
96 int h = rand.nextU() % n;
97 return SkIRect::MakeXYWH(x, y, w, h);
100 static void make_rand_rgn(SkRegion* rgn, SkRandom& rand) {
101 int count = rand.nextU() % 20;
102 for (int i = 0; i < count; ++i) {
103 rgn->op(rand_rect(rand, 100), SkRegion::kXOR_Op);
107 static bool operator==(const SkRegion& rgn, const SkAAClip& aaclip) {
110 copyToMask(rgn, &mask0);
111 aaclip.copyToMask(&mask1);
112 bool eq = (mask0 == mask1);
114 SkMask::FreeImage(mask0.fImage);
115 SkMask::FreeImage(mask1.fImage);
119 static bool equalsAAClip(const SkRegion& rgn) {
121 aaclip.setRegion(rgn);
122 return rgn == aaclip;
125 static void setRgnToPath(SkRegion* rgn, const SkPath& path) {
127 path.getBounds().round(&ir);
128 rgn->setPath(path, SkRegion(ir));
131 // aaclip.setRegion should create idential masks to the region
132 static void test_rgn(skiatest::Reporter* reporter) {
134 for (int i = 0; i < 1000; i++) {
136 make_rand_rgn(&rgn, rand);
137 REPORTER_ASSERT(reporter, equalsAAClip(rgn));
143 path.addCircle(0, 0, SkIntToScalar(30));
144 setRgnToPath(&rgn, path);
145 REPORTER_ASSERT(reporter, equalsAAClip(rgn));
149 path.lineTo(SkIntToScalar(100), 0);
150 path.lineTo(SkIntToScalar(100 - 20), SkIntToScalar(20));
151 path.lineTo(SkIntToScalar(20), SkIntToScalar(20));
152 setRgnToPath(&rgn, path);
153 REPORTER_ASSERT(reporter, equalsAAClip(rgn));
157 static const SkRegion::Op gRgnOps[] = {
158 SkRegion::kDifference_Op,
159 SkRegion::kIntersect_Op,
162 SkRegion::kReverseDifference_Op,
163 SkRegion::kReplace_Op
166 static const char* gRgnOpNames[] = {
167 "DIFF", "INTERSECT", "UNION", "XOR", "REVERSE_DIFF", "REPLACE"
170 static void imoveTo(SkPath& path, int x, int y) {
171 path.moveTo(SkIntToScalar(x), SkIntToScalar(y));
174 static void icubicTo(SkPath& path, int x0, int y0, int x1, int y1, int x2, int y2) {
175 path.cubicTo(SkIntToScalar(x0), SkIntToScalar(y0),
176 SkIntToScalar(x1), SkIntToScalar(y1),
177 SkIntToScalar(x2), SkIntToScalar(y2));
180 static void test_path_bounds(skiatest::Reporter* reporter) {
183 const int height = 40;
184 const SkScalar sheight = SkIntToScalar(height);
186 path.addOval(SkRect::MakeWH(sheight, sheight));
187 REPORTER_ASSERT(reporter, sheight == path.getBounds().height());
188 clip.setPath(path, NULL, true);
189 REPORTER_ASSERT(reporter, height == clip.getBounds().height());
191 // this is the trimmed height of this cubic (with aa). The critical thing
192 // for this test is that it is less than height, which represents just
193 // the bounds of the path's control-points.
195 // This used to fail until we tracked the MinY in the BuilderBlitter.
197 const int teardrop_height = 12;
199 imoveTo(path, 0, 20);
200 icubicTo(path, 40, 40, 40, 0, 0, 20);
201 REPORTER_ASSERT(reporter, sheight == path.getBounds().height());
202 clip.setPath(path, NULL, true);
203 REPORTER_ASSERT(reporter, teardrop_height == clip.getBounds().height());
206 static void test_empty(skiatest::Reporter* reporter) {
207 SkAAClip clip0, clip1;
209 REPORTER_ASSERT(reporter, clip0.isEmpty());
210 REPORTER_ASSERT(reporter, clip0.getBounds().isEmpty());
211 REPORTER_ASSERT(reporter, clip1 == clip0);
213 clip0.translate(10, 10); // should have no effect on empty
214 REPORTER_ASSERT(reporter, clip0.isEmpty());
215 REPORTER_ASSERT(reporter, clip0.getBounds().isEmpty());
216 REPORTER_ASSERT(reporter, clip1 == clip0);
218 SkIRect r = { 10, 10, 40, 50 };
220 REPORTER_ASSERT(reporter, !clip0.isEmpty());
221 REPORTER_ASSERT(reporter, !clip0.getBounds().isEmpty());
222 REPORTER_ASSERT(reporter, clip0 != clip1);
223 REPORTER_ASSERT(reporter, clip0.getBounds() == r);
226 REPORTER_ASSERT(reporter, clip0.isEmpty());
227 REPORTER_ASSERT(reporter, clip0.getBounds().isEmpty());
228 REPORTER_ASSERT(reporter, clip1 == clip0);
232 clip0.copyToMask(&mask);
233 REPORTER_ASSERT(reporter, NULL == mask.fImage);
234 REPORTER_ASSERT(reporter, mask.fBounds.isEmpty());
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);
245 static void test_irect(skiatest::Reporter* reporter) {
248 for (int i = 0; i < 10000; i++) {
249 SkAAClip clip0, clip1;
253 rand_irect(&r0, 10, rand);
254 rand_irect(&r1, 10, rand);
259 for (size_t j = 0; j < SK_ARRAY_COUNT(gRgnOps); ++j) {
260 SkRegion::Op op = gRgnOps[j];
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 SkDebugf("[%d %d %d %d] %s [%d %d %d %d] = BW:[%d %d %d %d] AA:[%d %d %d %d]\n",
267 r0.fLeft, r0.fTop, r0.right(), r0.bottom(),
269 r1.fLeft, r1.fTop, r1.right(), r1.bottom(),
270 rgn2.getBounds().fLeft, rgn2.getBounds().fTop,
271 rgn2.getBounds().right(), rgn2.getBounds().bottom(),
272 clip2.getBounds().fLeft, clip2.getBounds().fTop,
273 clip2.getBounds().right(), clip2.getBounds().bottom());
275 REPORTER_ASSERT(reporter, nonEmptyAA == nonEmptyBW);
276 REPORTER_ASSERT(reporter, clip2.getBounds() == rgn2.getBounds());
278 SkMask maskBW, maskAA;
279 copyToMask(rgn2, &maskBW);
280 clip2.copyToMask(&maskAA);
281 SkAutoMaskFreeImage freeBW(maskBW.fImage);
282 SkAutoMaskFreeImage freeAA(maskAA.fImage);
283 REPORTER_ASSERT(reporter, maskBW == maskAA);
288 static void test_path_with_hole(skiatest::Reporter* reporter) {
289 static const uint8_t gExpectedImage[] = {
290 0xFF, 0xFF, 0xFF, 0xFF,
291 0xFF, 0xFF, 0xFF, 0xFF,
292 0x00, 0x00, 0x00, 0x00,
293 0x00, 0x00, 0x00, 0x00,
294 0xFF, 0xFF, 0xFF, 0xFF,
295 0xFF, 0xFF, 0xFF, 0xFF,
298 expected.fBounds.set(0, 0, 4, 6);
299 expected.fRowBytes = 4;
300 expected.fFormat = SkMask::kA8_Format;
301 expected.fImage = (uint8_t*)gExpectedImage;
304 path.addRect(SkRect::MakeXYWH(0, 0,
305 SkIntToScalar(4), SkIntToScalar(2)));
306 path.addRect(SkRect::MakeXYWH(0, SkIntToScalar(4),
307 SkIntToScalar(4), SkIntToScalar(2)));
309 for (int i = 0; i < 2; ++i) {
311 clip.setPath(path, NULL, 1 == i);
314 clip.copyToMask(&mask);
315 SkAutoMaskFreeImage freeM(mask.fImage);
317 REPORTER_ASSERT(reporter, expected == mask);
321 static void test_really_a_rect(skiatest::Reporter* reporter) {
323 rrect.setRectXY(SkRect::MakeWH(100, 100), 5, 5);
326 path.addRRect(rrect);
331 REPORTER_ASSERT(reporter, clip.getBounds() == SkIRect::MakeWH(100, 100));
332 REPORTER_ASSERT(reporter, !clip.isRect());
334 // This rect should intersect the clip, but slice-out all of the "soft" parts,
335 // leaving just a rect.
336 const SkIRect ir = SkIRect::MakeLTRB(10, -10, 50, 90);
338 clip.op(ir, SkRegion::kIntersect_Op);
340 REPORTER_ASSERT(reporter, clip.getBounds() == SkIRect::MakeLTRB(10, 0, 50, 90));
341 // the clip recognized that that it is just a rect!
342 REPORTER_ASSERT(reporter, clip.isRect());
345 #include "SkRasterClip.h"
347 static void copyToMask(const SkRasterClip& rc, SkMask* mask) {
349 rc.aaRgn().copyToMask(mask);
351 copyToMask(rc.bwRgn(), mask);
355 static bool operator==(const SkRasterClip& a, const SkRasterClip& b) {
366 SkAutoMaskFreeImage aCleanUp(ma.fImage);
367 SkAutoMaskFreeImage bCleanUp(mb.fImage);
372 static void did_dx_affect(skiatest::Reporter* reporter, const SkScalar dx[],
373 size_t count, bool changed) {
374 const SkISize baseSize = SkISize::Make(10, 10);
375 SkIRect ir = { 0, 0, 10, 10 };
377 for (size_t i = 0; i < count; ++i) {
381 SkRasterClip rc0(ir);
382 SkRasterClip rc1(ir);
383 SkRasterClip rc2(ir);
385 rc0.op(r, baseSize, SkRegion::kIntersect_Op, false);
387 rc1.op(r, baseSize, SkRegion::kIntersect_Op, true);
388 r.offset(-2*dx[i], 0);
389 rc2.op(r, baseSize, SkRegion::kIntersect_Op, true);
391 REPORTER_ASSERT(reporter, changed != (rc0 == rc1));
392 REPORTER_ASSERT(reporter, changed != (rc0 == rc2));
396 static void test_nearly_integral(skiatest::Reporter* reporter) {
397 // All of these should generate equivalent rasterclips
399 static const SkScalar gSafeX[] = {
400 0, SK_Scalar1/1000, SK_Scalar1/100, SK_Scalar1/10,
402 did_dx_affect(reporter, gSafeX, SK_ARRAY_COUNT(gSafeX), false);
404 static const SkScalar gUnsafeX[] = {
405 SK_Scalar1/4, SK_Scalar1/3,
407 did_dx_affect(reporter, gUnsafeX, SK_ARRAY_COUNT(gUnsafeX), true);
410 static void test_regressions() {
411 // these should not assert in the debug build
412 // bug was introduced in rev. 3209
416 r.fLeft = 129.892181f;
417 r.fTop = 10.3999996f;
418 r.fRight = 130.892181f;
419 r.fBottom = 20.3999996f;
420 clip.setRect(r, true);
424 DEF_TEST(AAClip, reporter) {
425 test_empty(reporter);
426 test_path_bounds(reporter);
427 test_irect(reporter);
429 test_path_with_hole(reporter);
431 test_nearly_integral(reporter);
432 test_really_a_rect(reporter);