blob: 1392264b81c45d8cae4aa3e360f3c4a529d85462 [file] [log] [blame]
reed@google.com209c4152011-10-26 15:03:48 +00001/*
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"
mike@reedtribe.org95b85bd2011-11-23 03:12:58 +000010#include "SkCanvas.h"
11#include "SkMask.h"
reed@google.com209c4152011-10-26 15:03:48 +000012#include "SkPath.h"
reed@google.comd8676d22011-10-26 18:01:25 +000013#include "SkRandom.h"
14
mike@reedtribe.org95b85bd2011-11-23 03:12:58 +000015static 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 if (rgn.isEmpty()) {
61 mask->fImage = NULL;
62 mask->fBounds.setEmpty();
63 mask->fRowBytes = 0;
64 return;
65 }
66
67 mask->fBounds = rgn.getBounds();
68 mask->fRowBytes = mask->fBounds.width();
69 mask->fFormat = SkMask::kA8_Format;
70 mask->fImage = SkMask::AllocImage(mask->computeImageSize());
71 sk_bzero(mask->fImage, mask->computeImageSize());
72
73 SkBitmap bitmap;
74 bitmap.setConfig(SkBitmap::kA8_Config, mask->fBounds.width(),
75 mask->fBounds.height(), mask->fRowBytes);
76 bitmap.setPixels(mask->fImage);
77
78 // canvas expects its coordinate system to always be 0,0 in the top/left
79 // so we translate the rgn to match that before drawing into the mask.
80 //
81 SkRegion tmpRgn(rgn);
82 tmpRgn.translate(-rgn.getBounds().fLeft, -rgn.getBounds().fTop);
83
84 SkCanvas canvas(bitmap);
85 canvas.clipRegion(tmpRgn);
86 canvas.drawColor(SK_ColorBLACK);
87}
88
89static SkIRect rand_rect(SkRandom& rand, int n) {
90 int x = rand.nextS() % n;
91 int y = rand.nextS() % n;
92 int w = rand.nextU() % n;
93 int h = rand.nextU() % n;
94 return SkIRect::MakeXYWH(x, y, w, h);
95}
96
97static void make_rand_rgn(SkRegion* rgn, SkRandom& rand) {
98 int count = rand.nextU() % 20;
99 for (int i = 0; i < count; ++i) {
100 rgn->op(rand_rect(rand, 100), SkRegion::kXOR_Op);
101 }
102}
103
104// aaclip.setRegion should create idential masks to the region
105static void test_rgn(skiatest::Reporter* reporter) {
106 SkRandom rand;
107 for (int i = 0; i < 1000; i++) {
108 SkRegion rgn;
109 make_rand_rgn(&rgn, rand);
110 SkMask mask0;
111 copyToMask(rgn, &mask0);
112 SkAAClip aaclip;
113 aaclip.setRegion(rgn);
114 SkMask mask1;
115 aaclip.copyToMask(&mask1);
116
117 REPORTER_ASSERT(reporter, mask0 == mask1);
118
119 SkMask::FreeImage(mask0.fImage);
120 SkMask::FreeImage(mask1.fImage);
121 }
122}
123
reed@google.comd8676d22011-10-26 18:01:25 +0000124static const SkRegion::Op gRgnOps[] = {
reed@google.comc9041912011-10-27 16:58:46 +0000125 SkRegion::kDifference_Op,
reed@google.comd8676d22011-10-26 18:01:25 +0000126 SkRegion::kIntersect_Op,
127 SkRegion::kUnion_Op,
128 SkRegion::kXOR_Op,
reed@google.comc9041912011-10-27 16:58:46 +0000129 SkRegion::kReverseDifference_Op,
reed@google.comd8676d22011-10-26 18:01:25 +0000130 SkRegion::kReplace_Op
131};
132
133static const char* gRgnOpNames[] = {
reed@google.comc9041912011-10-27 16:58:46 +0000134 "DIFF", "INTERSECT", "UNION", "XOR", "REVERSE_DIFF", "REPLACE"
reed@google.comd8676d22011-10-26 18:01:25 +0000135};
reed@google.com209c4152011-10-26 15:03:48 +0000136
reed@google.com12e15252011-10-26 15:19:36 +0000137static void imoveTo(SkPath& path, int x, int y) {
138 path.moveTo(SkIntToScalar(x), SkIntToScalar(y));
139}
140
141static void icubicTo(SkPath& path, int x0, int y0, int x1, int y1, int x2, int y2) {
142 path.cubicTo(SkIntToScalar(x0), SkIntToScalar(y0),
143 SkIntToScalar(x1), SkIntToScalar(y1),
144 SkIntToScalar(x2), SkIntToScalar(y2));
145}
146
reed@google.comd8676d22011-10-26 18:01:25 +0000147static void test_path_bounds(skiatest::Reporter* reporter) {
reed@google.com209c4152011-10-26 15:03:48 +0000148 SkPath path;
149 SkAAClip clip;
150 const int height = 40;
151 const SkScalar sheight = SkIntToScalar(height);
152
153 path.addOval(SkRect::MakeWH(sheight, sheight));
154 REPORTER_ASSERT(reporter, sheight == path.getBounds().height());
155 clip.setPath(path, NULL, true);
156 REPORTER_ASSERT(reporter, height == clip.getBounds().height());
157
158 // this is the trimmed height of this cubic (with aa). The critical thing
159 // for this test is that it is less than height, which represents just
160 // the bounds of the path's control-points.
161 //
162 // This used to fail until we tracked the MinY in the BuilderBlitter.
163 //
164 const int teardrop_height = 12;
165 path.reset();
reed@google.com12e15252011-10-26 15:19:36 +0000166 imoveTo(path, 0, 20);
167 icubicTo(path, 40, 40, 40, 0, 0, 20);
reed@google.com209c4152011-10-26 15:03:48 +0000168 REPORTER_ASSERT(reporter, sheight == path.getBounds().height());
169 clip.setPath(path, NULL, true);
170 REPORTER_ASSERT(reporter, teardrop_height == clip.getBounds().height());
171}
172
reed@google.comd8676d22011-10-26 18:01:25 +0000173static void test_empty(skiatest::Reporter* reporter) {
174 SkAAClip clip0, clip1;
175
176 REPORTER_ASSERT(reporter, clip0.isEmpty());
177 REPORTER_ASSERT(reporter, clip0.getBounds().isEmpty());
178 REPORTER_ASSERT(reporter, clip1 == clip0);
179
180 clip0.translate(10, 10); // should have no effect on empty
181 REPORTER_ASSERT(reporter, clip0.isEmpty());
182 REPORTER_ASSERT(reporter, clip0.getBounds().isEmpty());
183 REPORTER_ASSERT(reporter, clip1 == clip0);
184
185 SkIRect r = { 10, 10, 40, 50 };
186 clip0.setRect(r);
187 REPORTER_ASSERT(reporter, !clip0.isEmpty());
188 REPORTER_ASSERT(reporter, !clip0.getBounds().isEmpty());
189 REPORTER_ASSERT(reporter, clip0 != clip1);
190 REPORTER_ASSERT(reporter, clip0.getBounds() == r);
191
192 clip0.setEmpty();
193 REPORTER_ASSERT(reporter, clip0.isEmpty());
194 REPORTER_ASSERT(reporter, clip0.getBounds().isEmpty());
195 REPORTER_ASSERT(reporter, clip1 == clip0);
196
197 SkMask mask;
198 mask.fImage = NULL;
199 clip0.copyToMask(&mask);
200 REPORTER_ASSERT(reporter, NULL == mask.fImage);
201 REPORTER_ASSERT(reporter, mask.fBounds.isEmpty());
202}
203
204static void rand_irect(SkIRect* r, int N, SkRandom& rand) {
205 r->setXYWH(0, 0, rand.nextU() % N, rand.nextU() % N);
206 int dx = rand.nextU() % (2*N);
207 int dy = rand.nextU() % (2*N);
208 // use int dx,dy to make the subtract be signed
209 r->offset(N - dx, N - dy);
210}
211
212static void test_irect(skiatest::Reporter* reporter) {
213 SkRandom rand;
214
reed@google.comc9041912011-10-27 16:58:46 +0000215 for (int i = 0; i < 10000; i++) {
reed@google.comd8676d22011-10-26 18:01:25 +0000216 SkAAClip clip0, clip1;
217 SkRegion rgn0, rgn1;
218 SkIRect r0, r1;
219
220 rand_irect(&r0, 10, rand);
221 rand_irect(&r1, 10, rand);
222 clip0.setRect(r0);
223 clip1.setRect(r1);
224 rgn0.setRect(r0);
225 rgn1.setRect(r1);
226 for (size_t j = 0; j < SK_ARRAY_COUNT(gRgnOps); ++j) {
227 SkRegion::Op op = gRgnOps[j];
228 SkAAClip clip2;
229 SkRegion rgn2;
230 bool nonEmptyAA = clip2.op(clip0, clip1, op);
231 bool nonEmptyBW = rgn2.op(rgn0, rgn1, op);
232 if (nonEmptyAA != nonEmptyBW || clip2.getBounds() != rgn2.getBounds()) {
233 SkDebugf("[%d %d %d %d] %s [%d %d %d %d] = BW:[%d %d %d %d] AA:[%d %d %d %d]\n",
234 r0.fLeft, r0.fTop, r0.right(), r0.bottom(),
235 gRgnOpNames[j],
236 r1.fLeft, r1.fTop, r1.right(), r1.bottom(),
237 rgn2.getBounds().fLeft, rgn2.getBounds().fTop,
238 rgn2.getBounds().right(), rgn2.getBounds().bottom(),
239 clip2.getBounds().fLeft, clip2.getBounds().fTop,
240 clip2.getBounds().right(), clip2.getBounds().bottom());
241 }
242 REPORTER_ASSERT(reporter, nonEmptyAA == nonEmptyBW);
243 REPORTER_ASSERT(reporter, clip2.getBounds() == rgn2.getBounds());
244 }
245 }
246}
247
reed@google.com209c4152011-10-26 15:03:48 +0000248static void TestAAClip(skiatest::Reporter* reporter) {
reed@google.comd8676d22011-10-26 18:01:25 +0000249 test_empty(reporter);
250 test_path_bounds(reporter);
251 test_irect(reporter);
mike@reedtribe.org95b85bd2011-11-23 03:12:58 +0000252 test_rgn(reporter);
reed@google.com209c4152011-10-26 15:03:48 +0000253}
254
255#include "TestClassDef.h"
256DEFINE_TESTCLASS("AAClip", AAClipTestClass, TestAAClip)