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 "Test.h"
9#include "SkAAClip.h"
10#include "SkCanvas.h"
11#include "SkMask.h"
12#include "SkPath.h"
13#include "SkRandom.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            SkASSERT(!"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            REPORTER_ASSERT(reporter, maskBW == maskAA);
280        }
281    }
282}
283
284static void test_path_with_hole(skiatest::Reporter* reporter) {
285    static const uint8_t gExpectedImage[] = {
286        0xFF, 0xFF, 0xFF, 0xFF,
287        0xFF, 0xFF, 0xFF, 0xFF,
288        0x00, 0x00, 0x00, 0x00,
289        0x00, 0x00, 0x00, 0x00,
290        0xFF, 0xFF, 0xFF, 0xFF,
291        0xFF, 0xFF, 0xFF, 0xFF,
292    };
293    SkMask expected;
294    expected.fBounds.set(0, 0, 4, 6);
295    expected.fRowBytes = 4;
296    expected.fFormat = SkMask::kA8_Format;
297    expected.fImage = (uint8_t*)gExpectedImage;
298
299    SkPath path;
300    path.addRect(SkRect::MakeXYWH(0, 0,
301                                  SkIntToScalar(4), SkIntToScalar(2)));
302    path.addRect(SkRect::MakeXYWH(0, SkIntToScalar(4),
303                                  SkIntToScalar(4), SkIntToScalar(2)));
304
305    for (int i = 0; i < 2; ++i) {
306        SkAAClip clip;
307        clip.setPath(path, NULL, 1 == i);
308
309        SkMask mask;
310        clip.copyToMask(&mask);
311
312        REPORTER_ASSERT(reporter, expected == mask);
313    }
314}
315
316static void test_regressions(skiatest::Reporter* reporter) {
317    // these should not assert in the debug build
318    // bug was introduced in rev. 3209
319    {
320        SkAAClip clip;
321        SkRect r;
322        r.fLeft = SkFloatToScalar(129.892181);
323        r.fTop = SkFloatToScalar(10.3999996);
324        r.fRight = SkFloatToScalar(130.892181);
325        r.fBottom = SkFloatToScalar(20.3999996);
326        clip.setRect(r, true);
327    }
328}
329
330static void TestAAClip(skiatest::Reporter* reporter) {
331    test_empty(reporter);
332    test_path_bounds(reporter);
333    test_irect(reporter);
334    test_rgn(reporter);
335    test_path_with_hole(reporter);
336    test_regressions(reporter);
337}
338
339#include "TestClassDef.h"
340DEFINE_TESTCLASS("AAClip", AAClipTestClass, TestAAClip)
341