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    SkImageInfo info = SkImageInfo::Make(mask->fBounds.width(),
75                                         mask->fBounds.height(),
76                                         kAlpha_8_SkColorType,
77                                         kPremul_SkAlphaType);
78    SkBitmap bitmap;
79    bitmap.installPixels(info, mask->fImage, mask->fRowBytes);
80
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.
83    //
84    SkRegion tmpRgn(rgn);
85    tmpRgn.translate(-rgn.getBounds().fLeft, -rgn.getBounds().fTop);
86
87    SkCanvas canvas(bitmap);
88    canvas.clipRegion(tmpRgn);
89    canvas.drawColor(SK_ColorBLACK);
90}
91
92static 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);
98}
99
100static 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);
104    }
105}
106
107static bool operator==(const SkRegion& rgn, const SkAAClip& aaclip) {
108    SkMask mask0, mask1;
109
110    copyToMask(rgn, &mask0);
111    aaclip.copyToMask(&mask1);
112    bool eq = (mask0 == mask1);
113
114    SkMask::FreeImage(mask0.fImage);
115    SkMask::FreeImage(mask1.fImage);
116    return eq;
117}
118
119static bool equalsAAClip(const SkRegion& rgn) {
120    SkAAClip aaclip;
121    aaclip.setRegion(rgn);
122    return rgn == aaclip;
123}
124
125static void setRgnToPath(SkRegion* rgn, const SkPath& path) {
126    SkIRect ir;
127    path.getBounds().round(&ir);
128    rgn->setPath(path, SkRegion(ir));
129}
130
131// aaclip.setRegion should create idential masks to the region
132static void test_rgn(skiatest::Reporter* reporter) {
133    SkRandom rand;
134    for (int i = 0; i < 1000; i++) {
135        SkRegion rgn;
136        make_rand_rgn(&rgn, rand);
137        REPORTER_ASSERT(reporter, equalsAAClip(rgn));
138    }
139
140    {
141        SkRegion rgn;
142        SkPath path;
143        path.addCircle(0, 0, SkIntToScalar(30));
144        setRgnToPath(&rgn, path);
145        REPORTER_ASSERT(reporter, equalsAAClip(rgn));
146
147        path.reset();
148        path.moveTo(0, 0);
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));
154    }
155}
156
157static const SkRegion::Op gRgnOps[] = {
158    SkRegion::kDifference_Op,
159    SkRegion::kIntersect_Op,
160    SkRegion::kUnion_Op,
161    SkRegion::kXOR_Op,
162    SkRegion::kReverseDifference_Op,
163    SkRegion::kReplace_Op
164};
165
166static const char* gRgnOpNames[] = {
167    "DIFF", "INTERSECT", "UNION", "XOR", "REVERSE_DIFF", "REPLACE"
168};
169
170static void imoveTo(SkPath& path, int x, int y) {
171    path.moveTo(SkIntToScalar(x), SkIntToScalar(y));
172}
173
174static 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));
178}
179
180static void test_path_bounds(skiatest::Reporter* reporter) {
181    SkPath path;
182    SkAAClip clip;
183    const int height = 40;
184    const SkScalar sheight = SkIntToScalar(height);
185
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());
190
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.
194    //
195    // This used to fail until we tracked the MinY in the BuilderBlitter.
196    //
197    const int teardrop_height = 12;
198    path.reset();
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());
204}
205
206static void test_empty(skiatest::Reporter* reporter) {
207    SkAAClip clip0, clip1;
208
209    REPORTER_ASSERT(reporter, clip0.isEmpty());
210    REPORTER_ASSERT(reporter, clip0.getBounds().isEmpty());
211    REPORTER_ASSERT(reporter, clip1 == clip0);
212
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);
217
218    SkIRect r = { 10, 10, 40, 50 };
219    clip0.setRect(r);
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);
224
225    clip0.setEmpty();
226    REPORTER_ASSERT(reporter, clip0.isEmpty());
227    REPORTER_ASSERT(reporter, clip0.getBounds().isEmpty());
228    REPORTER_ASSERT(reporter, clip1 == clip0);
229
230    SkMask mask;
231    mask.fImage = NULL;
232    clip0.copyToMask(&mask);
233    REPORTER_ASSERT(reporter, NULL == mask.fImage);
234    REPORTER_ASSERT(reporter, mask.fBounds.isEmpty());
235}
236
237static 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);
243}
244
245static void test_irect(skiatest::Reporter* reporter) {
246    SkRandom rand;
247
248    for (int i = 0; i < 10000; i++) {
249        SkAAClip clip0, clip1;
250        SkRegion rgn0, rgn1;
251        SkIRect r0, r1;
252
253        rand_irect(&r0, 10, rand);
254        rand_irect(&r1, 10, rand);
255        clip0.setRect(r0);
256        clip1.setRect(r1);
257        rgn0.setRect(r0);
258        rgn1.setRect(r1);
259        for (size_t j = 0; j < SK_ARRAY_COUNT(gRgnOps); ++j) {
260            SkRegion::Op op = gRgnOps[j];
261            SkAAClip clip2;
262            SkRegion rgn2;
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(),
268                         gRgnOpNames[j],
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());
274            }
275            REPORTER_ASSERT(reporter, nonEmptyAA == nonEmptyBW);
276            REPORTER_ASSERT(reporter, clip2.getBounds() == rgn2.getBounds());
277
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);
284        }
285    }
286}
287
288static 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,
296    };
297    SkMask expected;
298    expected.fBounds.set(0, 0, 4, 6);
299    expected.fRowBytes = 4;
300    expected.fFormat = SkMask::kA8_Format;
301    expected.fImage = (uint8_t*)gExpectedImage;
302
303    SkPath path;
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)));
308
309    for (int i = 0; i < 2; ++i) {
310        SkAAClip clip;
311        clip.setPath(path, NULL, 1 == i);
312
313        SkMask mask;
314        clip.copyToMask(&mask);
315        SkAutoMaskFreeImage freeM(mask.fImage);
316
317        REPORTER_ASSERT(reporter, expected == mask);
318    }
319}
320
321#include "SkRasterClip.h"
322
323static void copyToMask(const SkRasterClip& rc, SkMask* mask) {
324    if (rc.isAA()) {
325        rc.aaRgn().copyToMask(mask);
326    } else {
327        copyToMask(rc.bwRgn(), mask);
328    }
329}
330
331static bool operator==(const SkRasterClip& a, const SkRasterClip& b) {
332    if (a.isEmpty()) {
333        return b.isEmpty();
334    }
335    if (b.isEmpty()) {
336        return false;
337    }
338
339    SkMask ma, mb;
340    copyToMask(a, &ma);
341    copyToMask(b, &mb);
342    SkAutoMaskFreeImage aCleanUp(ma.fImage);
343    SkAutoMaskFreeImage bCleanUp(mb.fImage);
344
345    return ma == mb;
346}
347
348static void did_dx_affect(skiatest::Reporter* reporter, const SkScalar dx[],
349                          size_t count, bool changed) {
350    SkIRect ir = { 0, 0, 10, 10 };
351
352    for (size_t i = 0; i < count; ++i) {
353        SkRect r;
354        r.set(ir);
355
356        SkRasterClip rc0(ir);
357        SkRasterClip rc1(ir);
358        SkRasterClip rc2(ir);
359
360        rc0.op(r, SkRegion::kIntersect_Op, false);
361        r.offset(dx[i], 0);
362        rc1.op(r, SkRegion::kIntersect_Op, true);
363        r.offset(-2*dx[i], 0);
364        rc2.op(r, SkRegion::kIntersect_Op, true);
365
366        REPORTER_ASSERT(reporter, changed != (rc0 == rc1));
367        REPORTER_ASSERT(reporter, changed != (rc0 == rc2));
368    }
369}
370
371static void test_nearly_integral(skiatest::Reporter* reporter) {
372    // All of these should generate equivalent rasterclips
373
374    static const SkScalar gSafeX[] = {
375        0, SK_Scalar1/1000, SK_Scalar1/100, SK_Scalar1/10,
376    };
377    did_dx_affect(reporter, gSafeX, SK_ARRAY_COUNT(gSafeX), false);
378
379    static const SkScalar gUnsafeX[] = {
380        SK_Scalar1/4, SK_Scalar1/3,
381    };
382    did_dx_affect(reporter, gUnsafeX, SK_ARRAY_COUNT(gUnsafeX), true);
383}
384
385static void test_regressions() {
386    // these should not assert in the debug build
387    // bug was introduced in rev. 3209
388    {
389        SkAAClip clip;
390        SkRect r;
391        r.fLeft = 129.892181f;
392        r.fTop = 10.3999996f;
393        r.fRight = 130.892181f;
394        r.fBottom = 20.3999996f;
395        clip.setRect(r, true);
396    }
397}
398
399DEF_TEST(AAClip, reporter) {
400    test_empty(reporter);
401    test_path_bounds(reporter);
402    test_irect(reporter);
403    test_rgn(reporter);
404    test_path_with_hole(reporter);
405    test_regressions();
406    test_nearly_integral(reporter);
407}
408