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
321static void test_really_a_rect(skiatest::Reporter* reporter) {
322    SkRRect rrect;
323    rrect.setRectXY(SkRect::MakeWH(100, 100), 5, 5);
324
325    SkPath path;
326    path.addRRect(rrect);
327
328    SkAAClip clip;
329    clip.setPath(path);
330
331    REPORTER_ASSERT(reporter, clip.getBounds() == SkIRect::MakeWH(100, 100));
332    REPORTER_ASSERT(reporter, !clip.isRect());
333
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);
337
338    clip.op(ir, SkRegion::kIntersect_Op);
339
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());
343}
344
345#include "SkRasterClip.h"
346
347static void copyToMask(const SkRasterClip& rc, SkMask* mask) {
348    if (rc.isAA()) {
349        rc.aaRgn().copyToMask(mask);
350    } else {
351        copyToMask(rc.bwRgn(), mask);
352    }
353}
354
355static bool operator==(const SkRasterClip& a, const SkRasterClip& b) {
356    if (a.isEmpty()) {
357        return b.isEmpty();
358    }
359    if (b.isEmpty()) {
360        return false;
361    }
362
363    SkMask ma, mb;
364    copyToMask(a, &ma);
365    copyToMask(b, &mb);
366    SkAutoMaskFreeImage aCleanUp(ma.fImage);
367    SkAutoMaskFreeImage bCleanUp(mb.fImage);
368
369    return ma == mb;
370}
371
372static 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 };
376
377    for (size_t i = 0; i < count; ++i) {
378        SkRect r;
379        r.set(ir);
380
381        SkRasterClip rc0(ir);
382        SkRasterClip rc1(ir);
383        SkRasterClip rc2(ir);
384
385        rc0.op(r, baseSize, SkRegion::kIntersect_Op, false);
386        r.offset(dx[i], 0);
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);
390
391        REPORTER_ASSERT(reporter, changed != (rc0 == rc1));
392        REPORTER_ASSERT(reporter, changed != (rc0 == rc2));
393    }
394}
395
396static void test_nearly_integral(skiatest::Reporter* reporter) {
397    // All of these should generate equivalent rasterclips
398
399    static const SkScalar gSafeX[] = {
400        0, SK_Scalar1/1000, SK_Scalar1/100, SK_Scalar1/10,
401    };
402    did_dx_affect(reporter, gSafeX, SK_ARRAY_COUNT(gSafeX), false);
403
404    static const SkScalar gUnsafeX[] = {
405        SK_Scalar1/4, SK_Scalar1/3,
406    };
407    did_dx_affect(reporter, gUnsafeX, SK_ARRAY_COUNT(gUnsafeX), true);
408}
409
410static void test_regressions() {
411    // these should not assert in the debug build
412    // bug was introduced in rev. 3209
413    {
414        SkAAClip clip;
415        SkRect r;
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);
421    }
422}
423
424DEF_TEST(AAClip, reporter) {
425    test_empty(reporter);
426    test_path_bounds(reporter);
427    test_irect(reporter);
428    test_rgn(reporter);
429    test_path_with_hole(reporter);
430    test_regressions();
431    test_nearly_integral(reporter);
432    test_really_a_rect(reporter);
433}
434