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