blob: 11829d4d49e3a47308dd5d1abbc8f20cc73a60a5 [file] [log] [blame]
epoger@google.comec3ed6a2011-07-28 14:26:00 +00001
2/*
3 * Copyright 2011 Google Inc.
4 *
5 * Use of this source code is governed by a BSD-style license that can be
6 * found in the LICENSE file.
7 */
reed@android.com3abec1d2009-03-02 05:36:20 +00008#include "Test.h"
reed@google.com8cae8352012-09-14 15:18:41 +00009#include "SkCanvas.h"
reed@google.com55b5f4b2011-09-07 12:23:41 +000010#include "SkPaint.h"
reed@android.com3abec1d2009-03-02 05:36:20 +000011#include "SkPath.h"
reed@google.com04863fa2011-05-15 04:08:24 +000012#include "SkParse.h"
reed@google.com3e71a882012-01-10 18:44:37 +000013#include "SkParsePath.h"
reed@google.com8b06f1a2012-05-29 12:03:46 +000014#include "SkPathEffect.h"
schenney@chromium.org6630d8d2012-01-04 21:05:51 +000015#include "SkRandom.h"
reed@google.com53effc52011-09-21 19:05:12 +000016#include "SkReader32.h"
reed@android.com60bc6d52010-02-11 11:09:39 +000017#include "SkSize.h"
reed@google.com53effc52011-09-21 19:05:12 +000018#include "SkWriter32.h"
reed@google.com8cae8352012-09-14 15:18:41 +000019#include "SkSurface.h"
20
caryclark@google.com56f233a2012-11-19 13:06:06 +000021#if defined(WIN32)
22 #define SUPPRESS_VISIBILITY_WARNING
23#else
24 #define SUPPRESS_VISIBILITY_WARNING __attribute__((visibility("hidden")))
25#endif
26
reed@google.com8cae8352012-09-14 15:18:41 +000027static SkSurface* new_surface(int w, int h) {
28 SkImage::Info info = {
29 w, h, SkImage::kPMColor_ColorType, SkImage::kPremul_AlphaType
30 };
mike@reedtribe.orgb9476252012-11-15 02:37:45 +000031 return SkSurface::NewRaster(info);
reed@google.com8cae8352012-09-14 15:18:41 +000032}
33
reed@google.coma8790de2012-10-24 21:04:04 +000034// Make sure we stay non-finite once we get there (unless we reset or rewind).
35static void test_addrect_isfinite(skiatest::Reporter* reporter) {
36 SkPath path;
skia.committer@gmail.com8b0e2342012-10-25 02:01:20 +000037
reed@google.coma8790de2012-10-24 21:04:04 +000038 path.addRect(SkRect::MakeWH(50, 100));
39 REPORTER_ASSERT(reporter, path.isFinite());
40
41 path.moveTo(0, 0);
42 path.lineTo(SK_ScalarInfinity, 42);
43 REPORTER_ASSERT(reporter, !path.isFinite());
44
45 path.addRect(SkRect::MakeWH(50, 100));
46 REPORTER_ASSERT(reporter, !path.isFinite());
47
48 path.reset();
49 REPORTER_ASSERT(reporter, path.isFinite());
skia.committer@gmail.com8b0e2342012-10-25 02:01:20 +000050
reed@google.coma8790de2012-10-24 21:04:04 +000051 path.addRect(SkRect::MakeWH(50, 100));
52 REPORTER_ASSERT(reporter, path.isFinite());
53}
54
reed@google.com848148e2013-01-15 15:51:59 +000055static void build_big_path(SkPath* path, bool reducedCase) {
56 if (reducedCase) {
57 path->moveTo(577330, 1971.72f);
58 path->cubicTo(10.7082f, -116.596f, 262.057f, 45.6468f, 294.694f, 1.96237f);
59 } else {
60 path->moveTo(60.1631f, 7.70567f);
61 path->quadTo(60.1631f, 7.70567f, 0.99474f, 0.901199f);
62 path->lineTo(577379, 1977.77f);
63 path->quadTo(577364, 1979.57f, 577325, 1980.26f);
64 path->quadTo(577286, 1980.95f, 577245, 1980.13f);
65 path->quadTo(577205, 1979.3f, 577187, 1977.45f);
66 path->quadTo(577168, 1975.6f, 577183, 1973.8f);
67 path->quadTo(577198, 1972, 577238, 1971.31f);
68 path->quadTo(577277, 1970.62f, 577317, 1971.45f);
69 path->quadTo(577330, 1971.72f, 577341, 1972.11f);
70 path->cubicTo(10.7082f, -116.596f, 262.057f, 45.6468f, 294.694f, 1.96237f);
71 path->moveTo(306.718f, -32.912f);
72 path->cubicTo(30.531f, 10.0005f, 1502.47f, 13.2804f, 84.3088f, 9.99601f);
73 }
74}
75
76static void test_clipped_cubic(skiatest::Reporter* reporter) {
77 SkAutoTUnref<SkSurface> surface(new_surface(640, 480));
78
79 // This path used to assert, because our cubic-chopping code incorrectly
80 // moved control points after the chop. This test should be run in SK_DEBUG
81 // mode to ensure that we no long assert.
82 SkPath path;
83 for (int doReducedCase = 0; doReducedCase <= 1; ++doReducedCase) {
84 build_big_path(&path, SkToBool(doReducedCase));
85
86 SkPaint paint;
87 for (int doAA = 0; doAA <= 1; ++doAA) {
88 paint.setAntiAlias(SkToBool(doAA));
89 surface->getCanvas()->drawPath(path, paint);
90 }
91 }
92}
93
reed@google.com8cae8352012-09-14 15:18:41 +000094// Inspired by http://ie.microsoft.com/testdrive/Performance/Chalkboard/
95// which triggered an assert, from a tricky cubic. This test replicates that
96// example, so we can ensure that we handle it (in SkEdge.cpp), and don't
97// assert in the SK_DEBUG build.
98static void test_tricky_cubic(skiatest::Reporter* reporter) {
99 const SkPoint pts[] = {
skia.committer@gmail.com055c7c22012-09-15 02:01:41 +0000100 { SkDoubleToScalar(18.8943768), SkDoubleToScalar(129.121277) },
101 { SkDoubleToScalar(18.8937435), SkDoubleToScalar(129.121689) },
102 { SkDoubleToScalar(18.8950119), SkDoubleToScalar(129.120422) },
103 { SkDoubleToScalar(18.5030727), SkDoubleToScalar(129.13121) },
reed@google.com8cae8352012-09-14 15:18:41 +0000104 };
105
106 SkPath path;
107 path.moveTo(pts[0]);
108 path.cubicTo(pts[1], pts[2], pts[3]);
109
110 SkPaint paint;
111 paint.setAntiAlias(true);
112
113 SkSurface* surface = new_surface(19, 130);
114 surface->getCanvas()->drawPath(path, paint);
115 surface->unref();
116}
reed@android.com3abec1d2009-03-02 05:36:20 +0000117
tomhudson@google.comed02c4d2012-08-10 14:10:45 +0000118// Inspired by http://code.google.com/p/chromium/issues/detail?id=141651
119//
120static void test_isfinite_after_transform(skiatest::Reporter* reporter) {
121 SkPath path;
122 path.quadTo(157, 366, 286, 208);
123 path.arcTo(37, 442, 315, 163, 957494590897113.0f);
rmistry@google.comd6176b02012-08-23 18:14:13 +0000124
tomhudson@google.comed02c4d2012-08-10 14:10:45 +0000125 SkMatrix matrix;
126 matrix.setScale(1000*1000, 1000*1000);
127
128 // Be sure that path::transform correctly updates isFinite and the bounds
129 // if the transformation overflows. The previous bug was that isFinite was
130 // set to true in this case, but the bounds were not set to empty (which
131 // they should be).
132 while (path.isFinite()) {
133 REPORTER_ASSERT(reporter, path.getBounds().isFinite());
134 REPORTER_ASSERT(reporter, !path.getBounds().isEmpty());
135 path.transform(matrix);
136 }
137 REPORTER_ASSERT(reporter, path.getBounds().isEmpty());
138
139 matrix.setTranslate(SK_Scalar1, SK_Scalar1);
140 path.transform(matrix);
141 // we need to still be non-finite
142 REPORTER_ASSERT(reporter, !path.isFinite());
143 REPORTER_ASSERT(reporter, path.getBounds().isEmpty());
144}
145
skia.committer@gmail.com6a748ad2012-10-19 02:01:19 +0000146static void add_corner_arc(SkPath* path, const SkRect& rect,
147 SkScalar xIn, SkScalar yIn,
robertphillips@google.comb95eaa82012-10-18 15:26:12 +0000148 int startAngle)
149{
150
151 SkScalar rx = SkMinScalar(rect.width(), xIn);
152 SkScalar ry = SkMinScalar(rect.height(), yIn);
153
154 SkRect arcRect;
155 arcRect.set(-rx, -ry, rx, ry);
156 switch (startAngle) {
157 case 0:
158 arcRect.offset(rect.fRight - arcRect.fRight, rect.fBottom - arcRect.fBottom);
159 break;
160 case 90:
161 arcRect.offset(rect.fLeft - arcRect.fLeft, rect.fBottom - arcRect.fBottom);
162 break;
163 case 180:
164 arcRect.offset(rect.fLeft - arcRect.fLeft, rect.fTop - arcRect.fTop);
165 break;
166 case 270:
167 arcRect.offset(rect.fRight - arcRect.fRight, rect.fTop - arcRect.fTop);
168 break;
169 default:
170 break;
171 }
172
173 path->arcTo(arcRect, SkIntToScalar(startAngle), SkIntToScalar(90), false);
174}
175
skia.committer@gmail.com6a748ad2012-10-19 02:01:19 +0000176static void make_arb_round_rect(SkPath* path, const SkRect& r,
robertphillips@google.comb95eaa82012-10-18 15:26:12 +0000177 SkScalar xCorner, SkScalar yCorner) {
178 // we are lazy here and use the same x & y for each corner
179 add_corner_arc(path, r, xCorner, yCorner, 270);
180 add_corner_arc(path, r, xCorner, yCorner, 0);
181 add_corner_arc(path, r, xCorner, yCorner, 90);
182 add_corner_arc(path, r, xCorner, yCorner, 180);
robertphillips@google.com158618e2012-10-23 16:56:56 +0000183 path->close();
robertphillips@google.comb95eaa82012-10-18 15:26:12 +0000184}
185
186// Chrome creates its own round rects with each corner possibly being different.
187// Performance will suffer if they are not convex.
188// Note: PathBench::ArbRoundRectBench performs almost exactly
189// the same test (but with drawing)
190static void test_arb_round_rect_is_convex(skiatest::Reporter* reporter) {
191 SkRandom rand;
192 SkRect r;
193
194 for (int i = 0; i < 5000; ++i) {
195
robertphillips@google.com158618e2012-10-23 16:56:56 +0000196 SkScalar size = rand.nextUScalar1() * 30;
197 if (size < SK_Scalar1) {
robertphillips@google.comb95eaa82012-10-18 15:26:12 +0000198 continue;
199 }
200 r.fLeft = rand.nextUScalar1() * 300;
201 r.fTop = rand.nextUScalar1() * 300;
robertphillips@google.com158618e2012-10-23 16:56:56 +0000202 r.fRight = r.fLeft + 2 * size;
203 r.fBottom = r.fTop + 2 * size;
robertphillips@google.comb95eaa82012-10-18 15:26:12 +0000204
205 SkPath temp;
206
207 make_arb_round_rect(&temp, r, r.width() / 10, r.height() / 15);
208
robertphillips@google.comc7a37c72012-10-19 01:26:18 +0000209#ifdef SK_REDEFINE_ROOT2OVER2_TO_MAKE_ARCTOS_CONVEX
robertphillips@google.comb95eaa82012-10-18 15:26:12 +0000210 REPORTER_ASSERT(reporter, temp.isConvex());
robertphillips@google.comc7a37c72012-10-19 01:26:18 +0000211#endif
robertphillips@google.comb95eaa82012-10-18 15:26:12 +0000212 }
213}
214
robertphillips@google.com158618e2012-10-23 16:56:56 +0000215// Chrome will sometimes create a 0 radius round rect. The degenerate
216// quads prevent the path from being converted to a rect
217// Note: PathBench::ArbRoundRectBench performs almost exactly
218// the same test (but with drawing)
219static void test_arb_zero_rad_round_rect_is_rect(skiatest::Reporter* reporter) {
220 SkRandom rand;
221 SkRect r;
222
223 for (int i = 0; i < 5000; ++i) {
224
225 SkScalar size = rand.nextUScalar1() * 30;
226 if (size < SK_Scalar1) {
227 continue;
228 }
229 r.fLeft = rand.nextUScalar1() * 300;
230 r.fTop = rand.nextUScalar1() * 300;
231 r.fRight = r.fLeft + 2 * size;
232 r.fBottom = r.fTop + 2 * size;
233
234 SkPath temp;
235
236 make_arb_round_rect(&temp, r, 0, 0);
237
238#ifdef SK_REDEFINE_ROOT2OVER2_TO_MAKE_ARCTOS_CONVEX
239 SkRect result;
240 REPORTER_ASSERT(reporter, temp.isRect(&result));
241 REPORTER_ASSERT(reporter, r == result);
242#endif
243 }
244}
245
reed@google.com0bb18bb2012-07-26 15:20:36 +0000246static void test_rect_isfinite(skiatest::Reporter* reporter) {
247 const SkScalar inf = SK_ScalarInfinity;
248 const SkScalar nan = SK_ScalarNaN;
rmistry@google.comd6176b02012-08-23 18:14:13 +0000249
reed@google.com0bb18bb2012-07-26 15:20:36 +0000250 SkRect r;
251 r.setEmpty();
252 REPORTER_ASSERT(reporter, r.isFinite());
253 r.set(0, 0, inf, -inf);
254 REPORTER_ASSERT(reporter, !r.isFinite());
255 r.set(0, 0, nan, 0);
256 REPORTER_ASSERT(reporter, !r.isFinite());
rmistry@google.comd6176b02012-08-23 18:14:13 +0000257
reed@google.com0bb18bb2012-07-26 15:20:36 +0000258 SkPoint pts[] = {
259 { 0, 0 },
260 { SK_Scalar1, 0 },
261 { 0, SK_Scalar1 },
262 };
rmistry@google.comd6176b02012-08-23 18:14:13 +0000263
reed@google.com0bb18bb2012-07-26 15:20:36 +0000264 bool isFine = r.setBoundsCheck(pts, 3);
265 REPORTER_ASSERT(reporter, isFine);
266 REPORTER_ASSERT(reporter, !r.isEmpty());
rmistry@google.comd6176b02012-08-23 18:14:13 +0000267
reed@google.com0bb18bb2012-07-26 15:20:36 +0000268 pts[1].set(inf, 0);
269 isFine = r.setBoundsCheck(pts, 3);
270 REPORTER_ASSERT(reporter, !isFine);
271 REPORTER_ASSERT(reporter, r.isEmpty());
rmistry@google.comd6176b02012-08-23 18:14:13 +0000272
reed@google.com0bb18bb2012-07-26 15:20:36 +0000273 pts[1].set(nan, 0);
274 isFine = r.setBoundsCheck(pts, 3);
275 REPORTER_ASSERT(reporter, !isFine);
276 REPORTER_ASSERT(reporter, r.isEmpty());
277}
278
279static void test_path_isfinite(skiatest::Reporter* reporter) {
280 const SkScalar inf = SK_ScalarInfinity;
bsalomon@google.com50c79d82013-01-08 20:31:53 +0000281 const SkScalar negInf = SK_ScalarNegativeInfinity;
reed@google.com0bb18bb2012-07-26 15:20:36 +0000282 const SkScalar nan = SK_ScalarNaN;
rmistry@google.comd6176b02012-08-23 18:14:13 +0000283
reed@google.com0bb18bb2012-07-26 15:20:36 +0000284 SkPath path;
285 REPORTER_ASSERT(reporter, path.isFinite());
286
287 path.reset();
288 REPORTER_ASSERT(reporter, path.isFinite());
289
290 path.reset();
291 path.moveTo(SK_Scalar1, 0);
292 REPORTER_ASSERT(reporter, path.isFinite());
293
294 path.reset();
bsalomon@google.com50c79d82013-01-08 20:31:53 +0000295 path.moveTo(inf, negInf);
reed@google.com0bb18bb2012-07-26 15:20:36 +0000296 REPORTER_ASSERT(reporter, !path.isFinite());
297
298 path.reset();
299 path.moveTo(nan, 0);
300 REPORTER_ASSERT(reporter, !path.isFinite());
301}
302
303static void test_isfinite(skiatest::Reporter* reporter) {
304 test_rect_isfinite(reporter);
305 test_path_isfinite(reporter);
306}
307
reed@google.com744faba2012-05-29 19:54:52 +0000308// assert that we always
309// start with a moveTo
310// only have 1 moveTo
311// only have Lines after that
312// end with a single close
313// only have (at most) 1 close
314//
315static void test_poly(skiatest::Reporter* reporter, const SkPath& path,
316 const SkPoint srcPts[], int count, bool expectClose) {
317 SkPath::RawIter iter(path);
318 SkPoint pts[4];
reed@google.com744faba2012-05-29 19:54:52 +0000319
320 bool firstTime = true;
321 bool foundClose = false;
322 for (;;) {
323 switch (iter.next(pts)) {
324 case SkPath::kMove_Verb:
325 REPORTER_ASSERT(reporter, firstTime);
326 REPORTER_ASSERT(reporter, pts[0] == srcPts[0]);
327 srcPts++;
328 firstTime = false;
329 break;
330 case SkPath::kLine_Verb:
331 REPORTER_ASSERT(reporter, !firstTime);
332 REPORTER_ASSERT(reporter, pts[1] == srcPts[0]);
333 srcPts++;
334 break;
335 case SkPath::kQuad_Verb:
336 REPORTER_ASSERT(reporter, !"unexpected quad verb");
337 break;
338 case SkPath::kCubic_Verb:
339 REPORTER_ASSERT(reporter, !"unexpected cubic verb");
340 break;
341 case SkPath::kClose_Verb:
342 REPORTER_ASSERT(reporter, !firstTime);
343 REPORTER_ASSERT(reporter, !foundClose);
344 REPORTER_ASSERT(reporter, expectClose);
345 foundClose = true;
346 break;
347 case SkPath::kDone_Verb:
348 goto DONE;
349 }
350 }
351DONE:
352 REPORTER_ASSERT(reporter, foundClose == expectClose);
353}
354
355static void test_addPoly(skiatest::Reporter* reporter) {
356 SkPoint pts[32];
357 SkRandom rand;
rmistry@google.comd6176b02012-08-23 18:14:13 +0000358
reed@google.com744faba2012-05-29 19:54:52 +0000359 for (size_t i = 0; i < SK_ARRAY_COUNT(pts); ++i) {
360 pts[i].fX = rand.nextSScalar1();
361 pts[i].fY = rand.nextSScalar1();
362 }
363
364 for (int doClose = 0; doClose <= 1; ++doClose) {
365 for (size_t count = 1; count <= SK_ARRAY_COUNT(pts); ++count) {
366 SkPath path;
367 path.addPoly(pts, count, SkToBool(doClose));
368 test_poly(reporter, path, pts, count, SkToBool(doClose));
369 }
370 }
371}
372
reed@google.com8b06f1a2012-05-29 12:03:46 +0000373static void test_strokerec(skiatest::Reporter* reporter) {
374 SkStrokeRec rec(SkStrokeRec::kFill_InitStyle);
375 REPORTER_ASSERT(reporter, rec.isFillStyle());
rmistry@google.comd6176b02012-08-23 18:14:13 +0000376
reed@google.com8b06f1a2012-05-29 12:03:46 +0000377 rec.setHairlineStyle();
378 REPORTER_ASSERT(reporter, rec.isHairlineStyle());
rmistry@google.comd6176b02012-08-23 18:14:13 +0000379
reed@google.com8b06f1a2012-05-29 12:03:46 +0000380 rec.setStrokeStyle(SK_Scalar1, false);
381 REPORTER_ASSERT(reporter, SkStrokeRec::kStroke_Style == rec.getStyle());
rmistry@google.comd6176b02012-08-23 18:14:13 +0000382
reed@google.com8b06f1a2012-05-29 12:03:46 +0000383 rec.setStrokeStyle(SK_Scalar1, true);
384 REPORTER_ASSERT(reporter, SkStrokeRec::kStrokeAndFill_Style == rec.getStyle());
rmistry@google.comd6176b02012-08-23 18:14:13 +0000385
reed@google.com8b06f1a2012-05-29 12:03:46 +0000386 rec.setStrokeStyle(0, false);
387 REPORTER_ASSERT(reporter, SkStrokeRec::kHairline_Style == rec.getStyle());
rmistry@google.comd6176b02012-08-23 18:14:13 +0000388
reed@google.com8b06f1a2012-05-29 12:03:46 +0000389 rec.setStrokeStyle(0, true);
390 REPORTER_ASSERT(reporter, SkStrokeRec::kFill_Style == rec.getStyle());
391}
392
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000393// Set this for paths that don't have a consistent direction such as a bowtie.
394// (cheapComputeDirection is not expected to catch these.)
395static const SkPath::Direction kDontCheckDir = static_cast<SkPath::Direction>(-1);
396
397static void check_direction(skiatest::Reporter* reporter, const SkPath& path,
398 SkPath::Direction expected) {
399 if (expected == kDontCheckDir) {
400 return;
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +0000401 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000402 SkPath copy(path); // we make a copy so that we don't cache the result on the passed in path.
403
404 SkPath::Direction dir;
405 if (copy.cheapComputeDirection(&dir)) {
406 REPORTER_ASSERT(reporter, dir == expected);
407 } else {
408 REPORTER_ASSERT(reporter, SkPath::kUnknown_Direction == expected);
409 }
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +0000410}
411
reed@google.com3e71a882012-01-10 18:44:37 +0000412static void test_direction(skiatest::Reporter* reporter) {
413 size_t i;
414 SkPath path;
415 REPORTER_ASSERT(reporter, !path.cheapComputeDirection(NULL));
416 REPORTER_ASSERT(reporter, !path.cheapIsDirection(SkPath::kCW_Direction));
417 REPORTER_ASSERT(reporter, !path.cheapIsDirection(SkPath::kCCW_Direction));
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000418 REPORTER_ASSERT(reporter, path.cheapIsDirection(SkPath::kUnknown_Direction));
reed@google.com3e71a882012-01-10 18:44:37 +0000419
420 static const char* gDegen[] = {
421 "M 10 10",
422 "M 10 10 M 20 20",
423 "M 10 10 L 20 20",
424 "M 10 10 L 10 10 L 10 10",
425 "M 10 10 Q 10 10 10 10",
426 "M 10 10 C 10 10 10 10 10 10",
427 };
428 for (i = 0; i < SK_ARRAY_COUNT(gDegen); ++i) {
429 path.reset();
430 bool valid = SkParsePath::FromSVGString(gDegen[i], &path);
431 REPORTER_ASSERT(reporter, valid);
432 REPORTER_ASSERT(reporter, !path.cheapComputeDirection(NULL));
433 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000434
reed@google.com3e71a882012-01-10 18:44:37 +0000435 static const char* gCW[] = {
reed@google.comcabaf1d2012-01-11 21:03:05 +0000436 "M 10 10 L 10 10 Q 20 10 20 20",
reed@google.com3e71a882012-01-10 18:44:37 +0000437 "M 10 10 C 20 10 20 20 20 20",
reed@google.comd4146662012-01-31 15:42:29 +0000438 "M 20 10 Q 20 20 30 20 L 10 20", // test double-back at y-max
bsalomon@google.com4eefe612012-07-10 18:28:12 +0000439 // rect with top two corners replaced by cubics with identical middle
440 // control points
reed@google.com20fb0c72012-11-13 13:47:39 +0000441 "M 10 10 C 10 0 10 0 20 0 L 40 0 C 50 0 50 0 50 10",
442 "M 20 10 L 0 10 Q 10 10 20 0", // left, degenerate serif
reed@google.com3e71a882012-01-10 18:44:37 +0000443 };
444 for (i = 0; i < SK_ARRAY_COUNT(gCW); ++i) {
445 path.reset();
446 bool valid = SkParsePath::FromSVGString(gCW[i], &path);
447 REPORTER_ASSERT(reporter, valid);
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000448 check_direction(reporter, path, SkPath::kCW_Direction);
reed@google.com3e71a882012-01-10 18:44:37 +0000449 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000450
reed@google.com3e71a882012-01-10 18:44:37 +0000451 static const char* gCCW[] = {
reed@google.comcabaf1d2012-01-11 21:03:05 +0000452 "M 10 10 L 10 10 Q 20 10 20 -20",
reed@google.com3e71a882012-01-10 18:44:37 +0000453 "M 10 10 C 20 10 20 -20 20 -20",
reed@google.comd4146662012-01-31 15:42:29 +0000454 "M 20 10 Q 20 20 10 20 L 30 20", // test double-back at y-max
bsalomon@google.com4eefe612012-07-10 18:28:12 +0000455 // rect with top two corners replaced by cubics with identical middle
456 // control points
reed@google.com20fb0c72012-11-13 13:47:39 +0000457 "M 50 10 C 50 0 50 0 40 0 L 20 0 C 10 0 10 0 10 10",
458 "M 10 10 L 30 10 Q 20 10 10 0", // right, degenerate serif
reed@google.com3e71a882012-01-10 18:44:37 +0000459 };
460 for (i = 0; i < SK_ARRAY_COUNT(gCCW); ++i) {
461 path.reset();
462 bool valid = SkParsePath::FromSVGString(gCCW[i], &path);
463 REPORTER_ASSERT(reporter, valid);
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000464 check_direction(reporter, path, SkPath::kCCW_Direction);
reed@google.com3e71a882012-01-10 18:44:37 +0000465 }
reed@google.comac8543f2012-01-30 20:51:25 +0000466
467 // Test two donuts, each wound a different direction. Only the outer contour
468 // determines the cheap direction
469 path.reset();
470 path.addCircle(0, 0, SkIntToScalar(2), SkPath::kCW_Direction);
471 path.addCircle(0, 0, SkIntToScalar(1), SkPath::kCCW_Direction);
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000472 check_direction(reporter, path, SkPath::kCW_Direction);
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +0000473
reed@google.comac8543f2012-01-30 20:51:25 +0000474 path.reset();
475 path.addCircle(0, 0, SkIntToScalar(1), SkPath::kCW_Direction);
476 path.addCircle(0, 0, SkIntToScalar(2), SkPath::kCCW_Direction);
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000477 check_direction(reporter, path, SkPath::kCCW_Direction);
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +0000478
bsalomon@google.com6843ac42012-02-17 13:49:03 +0000479#ifdef SK_SCALAR_IS_FLOAT
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +0000480 // triangle with one point really far from the origin.
481 path.reset();
482 // the first point is roughly 1.05e10, 1.05e10
bsalomon@google.com53aab782012-02-23 14:54:49 +0000483 path.moveTo(SkFloatToScalar(SkBits2Float(0x501c7652)), SkFloatToScalar(SkBits2Float(0x501c7652)));
484 path.lineTo(110 * SK_Scalar1, -10 * SK_Scalar1);
485 path.lineTo(-10 * SK_Scalar1, 60 * SK_Scalar1);
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000486 check_direction(reporter, path, SkPath::kCCW_Direction);
bsalomon@google.com53aab782012-02-23 14:54:49 +0000487#endif
reed@google.com3e71a882012-01-10 18:44:37 +0000488}
489
reed@google.comffdb0182011-11-14 19:29:14 +0000490static void add_rect(SkPath* path, const SkRect& r) {
491 path->moveTo(r.fLeft, r.fTop);
492 path->lineTo(r.fRight, r.fTop);
493 path->lineTo(r.fRight, r.fBottom);
494 path->lineTo(r.fLeft, r.fBottom);
495 path->close();
496}
497
498static void test_bounds(skiatest::Reporter* reporter) {
499 static const SkRect rects[] = {
reed@google.com3563c9e2011-11-14 19:34:57 +0000500 { SkIntToScalar(10), SkIntToScalar(160), SkIntToScalar(610), SkIntToScalar(160) },
501 { SkIntToScalar(610), SkIntToScalar(160), SkIntToScalar(610), SkIntToScalar(199) },
502 { SkIntToScalar(10), SkIntToScalar(198), SkIntToScalar(610), SkIntToScalar(199) },
503 { SkIntToScalar(10), SkIntToScalar(160), SkIntToScalar(10), SkIntToScalar(199) },
reed@google.comffdb0182011-11-14 19:29:14 +0000504 };
505
506 SkPath path0, path1;
507 for (size_t i = 0; i < SK_ARRAY_COUNT(rects); ++i) {
508 path0.addRect(rects[i]);
509 add_rect(&path1, rects[i]);
510 }
511
512 REPORTER_ASSERT(reporter, path0.getBounds() == path1.getBounds());
513}
514
reed@google.com55b5f4b2011-09-07 12:23:41 +0000515static void stroke_cubic(const SkPoint pts[4]) {
516 SkPath path;
517 path.moveTo(pts[0]);
518 path.cubicTo(pts[1], pts[2], pts[3]);
rmistry@google.comd6176b02012-08-23 18:14:13 +0000519
reed@google.com55b5f4b2011-09-07 12:23:41 +0000520 SkPaint paint;
521 paint.setStyle(SkPaint::kStroke_Style);
522 paint.setStrokeWidth(SK_Scalar1 * 2);
rmistry@google.comd6176b02012-08-23 18:14:13 +0000523
reed@google.com55b5f4b2011-09-07 12:23:41 +0000524 SkPath fill;
525 paint.getFillPath(path, &fill);
526}
527
528// just ensure this can run w/o any SkASSERTS firing in the debug build
529// we used to assert due to differences in how we determine a degenerate vector
530// but that was fixed with the introduction of SkPoint::CanNormalize
531static void stroke_tiny_cubic() {
532 SkPoint p0[] = {
533 { 372.0f, 92.0f },
534 { 372.0f, 92.0f },
535 { 372.0f, 92.0f },
536 { 372.0f, 92.0f },
537 };
rmistry@google.comd6176b02012-08-23 18:14:13 +0000538
reed@google.com55b5f4b2011-09-07 12:23:41 +0000539 stroke_cubic(p0);
rmistry@google.comd6176b02012-08-23 18:14:13 +0000540
reed@google.com55b5f4b2011-09-07 12:23:41 +0000541 SkPoint p1[] = {
542 { 372.0f, 92.0f },
543 { 372.0007f, 92.000755f },
544 { 371.99927f, 92.003922f },
545 { 371.99826f, 92.003899f },
546 };
rmistry@google.comd6176b02012-08-23 18:14:13 +0000547
reed@google.com55b5f4b2011-09-07 12:23:41 +0000548 stroke_cubic(p1);
549}
550
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +0000551static void check_close(skiatest::Reporter* reporter, const SkPath& path) {
552 for (int i = 0; i < 2; ++i) {
robertphillips@google.com09042b82012-04-06 20:01:46 +0000553 SkPath::Iter iter(path, SkToBool(i));
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +0000554 SkPoint mv;
555 SkPoint pts[4];
556 SkPath::Verb v;
557 int nMT = 0;
558 int nCL = 0;
tomhudson@google.com221db3c2011-07-28 21:10:29 +0000559 mv.set(0, 0);
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +0000560 while (SkPath::kDone_Verb != (v = iter.next(pts))) {
561 switch (v) {
562 case SkPath::kMove_Verb:
563 mv = pts[0];
564 ++nMT;
565 break;
566 case SkPath::kClose_Verb:
567 REPORTER_ASSERT(reporter, mv == pts[0]);
568 ++nCL;
569 break;
570 default:
571 break;
572 }
573 }
574 // if we force a close on the interator we should have a close
575 // for every moveTo
576 REPORTER_ASSERT(reporter, !i || nMT == nCL);
577 }
578}
579
580static void test_close(skiatest::Reporter* reporter) {
581 SkPath closePt;
582 closePt.moveTo(0, 0);
583 closePt.close();
584 check_close(reporter, closePt);
585
586 SkPath openPt;
587 openPt.moveTo(0, 0);
588 check_close(reporter, openPt);
589
590 SkPath empty;
591 check_close(reporter, empty);
592 empty.close();
593 check_close(reporter, empty);
594
595 SkPath rect;
596 rect.addRect(SK_Scalar1, SK_Scalar1, 10 * SK_Scalar1, 10*SK_Scalar1);
597 check_close(reporter, rect);
598 rect.close();
599 check_close(reporter, rect);
600
601 SkPath quad;
602 quad.quadTo(SK_Scalar1, SK_Scalar1, 10 * SK_Scalar1, 10*SK_Scalar1);
603 check_close(reporter, quad);
604 quad.close();
605 check_close(reporter, quad);
606
607 SkPath cubic;
rmistry@google.comd6176b02012-08-23 18:14:13 +0000608 quad.cubicTo(SK_Scalar1, SK_Scalar1, 10 * SK_Scalar1,
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +0000609 10*SK_Scalar1, 20 * SK_Scalar1, 20*SK_Scalar1);
610 check_close(reporter, cubic);
611 cubic.close();
612 check_close(reporter, cubic);
613
614 SkPath line;
615 line.moveTo(SK_Scalar1, SK_Scalar1);
616 line.lineTo(10 * SK_Scalar1, 10*SK_Scalar1);
617 check_close(reporter, line);
618 line.close();
619 check_close(reporter, line);
620
621 SkPath rect2;
622 rect2.addRect(SK_Scalar1, SK_Scalar1, 10 * SK_Scalar1, 10*SK_Scalar1);
623 rect2.close();
624 rect2.addRect(SK_Scalar1, SK_Scalar1, 10 * SK_Scalar1, 10*SK_Scalar1);
625 check_close(reporter, rect2);
626 rect2.close();
627 check_close(reporter, rect2);
628
629 SkPath oval3;
630 oval3.addOval(SkRect::MakeWH(SK_Scalar1*100,SK_Scalar1*100));
631 oval3.close();
632 oval3.addOval(SkRect::MakeWH(SK_Scalar1*200,SK_Scalar1*200));
633 check_close(reporter, oval3);
634 oval3.close();
635 check_close(reporter, oval3);
636
637 SkPath moves;
638 moves.moveTo(SK_Scalar1, SK_Scalar1);
639 moves.moveTo(5 * SK_Scalar1, SK_Scalar1);
640 moves.moveTo(SK_Scalar1, 10 * SK_Scalar1);
641 moves.moveTo(10 *SK_Scalar1, SK_Scalar1);
642 check_close(reporter, moves);
reed@google.com55b5f4b2011-09-07 12:23:41 +0000643
644 stroke_tiny_cubic();
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +0000645}
646
reed@google.com7c424812011-05-15 04:38:34 +0000647static void check_convexity(skiatest::Reporter* reporter, const SkPath& path,
648 SkPath::Convexity expected) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000649 SkPath copy(path); // we make a copy so that we don't cache the result on the passed in path.
650 SkPath::Convexity c = copy.getConvexity();
reed@google.com7c424812011-05-15 04:38:34 +0000651 REPORTER_ASSERT(reporter, c == expected);
652}
653
654static void test_convexity2(skiatest::Reporter* reporter) {
655 SkPath pt;
656 pt.moveTo(0, 0);
657 pt.close();
reed@google.comb54455e2011-05-16 14:16:04 +0000658 check_convexity(reporter, pt, SkPath::kConvex_Convexity);
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000659 check_direction(reporter, pt, SkPath::kUnknown_Direction);
rmistry@google.comd6176b02012-08-23 18:14:13 +0000660
reed@google.com7c424812011-05-15 04:38:34 +0000661 SkPath line;
schenney@chromium.org6c31d9d2011-12-20 16:33:30 +0000662 line.moveTo(12*SK_Scalar1, 20*SK_Scalar1);
663 line.lineTo(-12*SK_Scalar1, -20*SK_Scalar1);
reed@google.com7c424812011-05-15 04:38:34 +0000664 line.close();
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000665 check_convexity(reporter, line, SkPath::kConvex_Convexity);
666 check_direction(reporter, line, SkPath::kUnknown_Direction);
rmistry@google.comd6176b02012-08-23 18:14:13 +0000667
reed@google.com7c424812011-05-15 04:38:34 +0000668 SkPath triLeft;
669 triLeft.moveTo(0, 0);
schenney@chromium.org6c31d9d2011-12-20 16:33:30 +0000670 triLeft.lineTo(SK_Scalar1, 0);
671 triLeft.lineTo(SK_Scalar1, SK_Scalar1);
reed@google.com7c424812011-05-15 04:38:34 +0000672 triLeft.close();
673 check_convexity(reporter, triLeft, SkPath::kConvex_Convexity);
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000674 check_direction(reporter, triLeft, SkPath::kCW_Direction);
rmistry@google.comd6176b02012-08-23 18:14:13 +0000675
reed@google.com7c424812011-05-15 04:38:34 +0000676 SkPath triRight;
677 triRight.moveTo(0, 0);
schenney@chromium.org6c31d9d2011-12-20 16:33:30 +0000678 triRight.lineTo(-SK_Scalar1, 0);
679 triRight.lineTo(SK_Scalar1, SK_Scalar1);
reed@google.com7c424812011-05-15 04:38:34 +0000680 triRight.close();
681 check_convexity(reporter, triRight, SkPath::kConvex_Convexity);
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000682 check_direction(reporter, triRight, SkPath::kCCW_Direction);
rmistry@google.comd6176b02012-08-23 18:14:13 +0000683
reed@google.com7c424812011-05-15 04:38:34 +0000684 SkPath square;
685 square.moveTo(0, 0);
schenney@chromium.org6c31d9d2011-12-20 16:33:30 +0000686 square.lineTo(SK_Scalar1, 0);
687 square.lineTo(SK_Scalar1, SK_Scalar1);
688 square.lineTo(0, SK_Scalar1);
reed@google.com7c424812011-05-15 04:38:34 +0000689 square.close();
690 check_convexity(reporter, square, SkPath::kConvex_Convexity);
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000691 check_direction(reporter, square, SkPath::kCW_Direction);
rmistry@google.comd6176b02012-08-23 18:14:13 +0000692
reed@google.com7c424812011-05-15 04:38:34 +0000693 SkPath redundantSquare;
694 redundantSquare.moveTo(0, 0);
695 redundantSquare.lineTo(0, 0);
696 redundantSquare.lineTo(0, 0);
schenney@chromium.org6c31d9d2011-12-20 16:33:30 +0000697 redundantSquare.lineTo(SK_Scalar1, 0);
698 redundantSquare.lineTo(SK_Scalar1, 0);
699 redundantSquare.lineTo(SK_Scalar1, 0);
700 redundantSquare.lineTo(SK_Scalar1, SK_Scalar1);
701 redundantSquare.lineTo(SK_Scalar1, SK_Scalar1);
702 redundantSquare.lineTo(SK_Scalar1, SK_Scalar1);
703 redundantSquare.lineTo(0, SK_Scalar1);
704 redundantSquare.lineTo(0, SK_Scalar1);
705 redundantSquare.lineTo(0, SK_Scalar1);
reed@google.com7c424812011-05-15 04:38:34 +0000706 redundantSquare.close();
707 check_convexity(reporter, redundantSquare, SkPath::kConvex_Convexity);
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000708 check_direction(reporter, redundantSquare, SkPath::kCW_Direction);
rmistry@google.comd6176b02012-08-23 18:14:13 +0000709
reed@google.com7c424812011-05-15 04:38:34 +0000710 SkPath bowTie;
711 bowTie.moveTo(0, 0);
712 bowTie.lineTo(0, 0);
713 bowTie.lineTo(0, 0);
schenney@chromium.org6c31d9d2011-12-20 16:33:30 +0000714 bowTie.lineTo(SK_Scalar1, SK_Scalar1);
715 bowTie.lineTo(SK_Scalar1, SK_Scalar1);
716 bowTie.lineTo(SK_Scalar1, SK_Scalar1);
717 bowTie.lineTo(SK_Scalar1, 0);
718 bowTie.lineTo(SK_Scalar1, 0);
719 bowTie.lineTo(SK_Scalar1, 0);
720 bowTie.lineTo(0, SK_Scalar1);
721 bowTie.lineTo(0, SK_Scalar1);
722 bowTie.lineTo(0, SK_Scalar1);
reed@google.com7c424812011-05-15 04:38:34 +0000723 bowTie.close();
724 check_convexity(reporter, bowTie, SkPath::kConcave_Convexity);
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000725 check_direction(reporter, bowTie, kDontCheckDir);
rmistry@google.comd6176b02012-08-23 18:14:13 +0000726
reed@google.com7c424812011-05-15 04:38:34 +0000727 SkPath spiral;
728 spiral.moveTo(0, 0);
schenney@chromium.org6c31d9d2011-12-20 16:33:30 +0000729 spiral.lineTo(100*SK_Scalar1, 0);
730 spiral.lineTo(100*SK_Scalar1, 100*SK_Scalar1);
731 spiral.lineTo(0, 100*SK_Scalar1);
732 spiral.lineTo(0, 50*SK_Scalar1);
733 spiral.lineTo(50*SK_Scalar1, 50*SK_Scalar1);
734 spiral.lineTo(50*SK_Scalar1, 75*SK_Scalar1);
reed@google.com7c424812011-05-15 04:38:34 +0000735 spiral.close();
reed@google.com85b6e392011-05-15 20:25:17 +0000736 check_convexity(reporter, spiral, SkPath::kConcave_Convexity);
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000737 check_direction(reporter, spiral, kDontCheckDir);
rmistry@google.comd6176b02012-08-23 18:14:13 +0000738
reed@google.com7c424812011-05-15 04:38:34 +0000739 SkPath dent;
schenney@chromium.org6c31d9d2011-12-20 16:33:30 +0000740 dent.moveTo(0, 0);
741 dent.lineTo(100*SK_Scalar1, 100*SK_Scalar1);
742 dent.lineTo(0, 100*SK_Scalar1);
743 dent.lineTo(-50*SK_Scalar1, 200*SK_Scalar1);
744 dent.lineTo(-200*SK_Scalar1, 100*SK_Scalar1);
reed@google.com7c424812011-05-15 04:38:34 +0000745 dent.close();
746 check_convexity(reporter, dent, SkPath::kConcave_Convexity);
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000747 check_direction(reporter, dent, SkPath::kCW_Direction);
reed@google.com7c424812011-05-15 04:38:34 +0000748}
749
reed@android.com6b82d1a2009-06-03 02:35:01 +0000750static void check_convex_bounds(skiatest::Reporter* reporter, const SkPath& p,
751 const SkRect& bounds) {
752 REPORTER_ASSERT(reporter, p.isConvex());
753 REPORTER_ASSERT(reporter, p.getBounds() == bounds);
reed@google.com62047cf2011-02-07 19:39:09 +0000754
reed@android.com6b82d1a2009-06-03 02:35:01 +0000755 SkPath p2(p);
756 REPORTER_ASSERT(reporter, p2.isConvex());
757 REPORTER_ASSERT(reporter, p2.getBounds() == bounds);
758
759 SkPath other;
760 other.swap(p2);
761 REPORTER_ASSERT(reporter, other.isConvex());
762 REPORTER_ASSERT(reporter, other.getBounds() == bounds);
763}
764
reed@google.com04863fa2011-05-15 04:08:24 +0000765static void setFromString(SkPath* path, const char str[]) {
766 bool first = true;
767 while (str) {
768 SkScalar x, y;
769 str = SkParse::FindScalar(str, &x);
770 if (NULL == str) {
771 break;
772 }
773 str = SkParse::FindScalar(str, &y);
774 SkASSERT(str);
775 if (first) {
776 path->moveTo(x, y);
777 first = false;
778 } else {
779 path->lineTo(x, y);
780 }
781 }
782}
783
784static void test_convexity(skiatest::Reporter* reporter) {
reed@google.com04863fa2011-05-15 04:08:24 +0000785 SkPath path;
786
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000787 check_convexity(reporter, path, SkPath::kConvex_Convexity);
reed@google.come3543972012-01-10 18:59:22 +0000788 path.addCircle(0, 0, SkIntToScalar(10));
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000789 check_convexity(reporter, path, SkPath::kConvex_Convexity);
reed@google.come3543972012-01-10 18:59:22 +0000790 path.addCircle(0, 0, SkIntToScalar(10)); // 2nd circle
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000791 check_convexity(reporter, path, SkPath::kConcave_Convexity);
792
reed@google.com04863fa2011-05-15 04:08:24 +0000793 path.reset();
reed@google.come3543972012-01-10 18:59:22 +0000794 path.addRect(0, 0, SkIntToScalar(10), SkIntToScalar(10), SkPath::kCCW_Direction);
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000795 check_convexity(reporter, path, SkPath::kConvex_Convexity);
reed@google.com3e71a882012-01-10 18:44:37 +0000796 REPORTER_ASSERT(reporter, path.cheapIsDirection(SkPath::kCCW_Direction));
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000797
reed@google.com04863fa2011-05-15 04:08:24 +0000798 path.reset();
reed@google.come3543972012-01-10 18:59:22 +0000799 path.addRect(0, 0, SkIntToScalar(10), SkIntToScalar(10), SkPath::kCW_Direction);
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000800 check_convexity(reporter, path, SkPath::kConvex_Convexity);
reed@google.com3e71a882012-01-10 18:44:37 +0000801 REPORTER_ASSERT(reporter, path.cheapIsDirection(SkPath::kCW_Direction));
rmistry@google.comd6176b02012-08-23 18:14:13 +0000802
reed@google.com04863fa2011-05-15 04:08:24 +0000803 static const struct {
804 const char* fPathStr;
805 SkPath::Convexity fExpectedConvexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000806 SkPath::Direction fExpectedDirection;
reed@google.com04863fa2011-05-15 04:08:24 +0000807 } gRec[] = {
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000808 { "", SkPath::kConvex_Convexity, SkPath::kUnknown_Direction },
809 { "0 0", SkPath::kConvex_Convexity, SkPath::kUnknown_Direction },
810 { "0 0 10 10", SkPath::kConvex_Convexity, SkPath::kUnknown_Direction },
811 { "0 0 10 10 20 20 0 0 10 10", SkPath::kConcave_Convexity, SkPath::kUnknown_Direction },
812 { "0 0 10 10 10 20", SkPath::kConvex_Convexity, SkPath::kCW_Direction },
813 { "0 0 10 10 10 0", SkPath::kConvex_Convexity, SkPath::kCCW_Direction },
814 { "0 0 10 10 10 0 0 10", SkPath::kConcave_Convexity, kDontCheckDir },
815 { "0 0 10 0 0 10 -10 -10", SkPath::kConcave_Convexity, SkPath::kCW_Direction },
reed@google.com04863fa2011-05-15 04:08:24 +0000816 };
817
818 for (size_t i = 0; i < SK_ARRAY_COUNT(gRec); ++i) {
819 SkPath path;
820 setFromString(&path, gRec[i].fPathStr);
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000821 check_convexity(reporter, path, gRec[i].fExpectedConvexity);
822 check_direction(reporter, path, gRec[i].fExpectedDirection);
reed@google.com04863fa2011-05-15 04:08:24 +0000823 }
824}
825
reed@google.com7e6c4d12012-05-10 14:05:43 +0000826static void test_isLine(skiatest::Reporter* reporter) {
827 SkPath path;
828 SkPoint pts[2];
829 const SkScalar value = SkIntToScalar(5);
830
831 REPORTER_ASSERT(reporter, !path.isLine(NULL));
rmistry@google.comd6176b02012-08-23 18:14:13 +0000832
reed@google.com7e6c4d12012-05-10 14:05:43 +0000833 // set some non-zero values
834 pts[0].set(value, value);
835 pts[1].set(value, value);
836 REPORTER_ASSERT(reporter, !path.isLine(pts));
837 // check that pts was untouched
838 REPORTER_ASSERT(reporter, pts[0].equals(value, value));
839 REPORTER_ASSERT(reporter, pts[1].equals(value, value));
840
841 const SkScalar moveX = SkIntToScalar(1);
842 const SkScalar moveY = SkIntToScalar(2);
843 SkASSERT(value != moveX && value != moveY);
844
845 path.moveTo(moveX, moveY);
846 REPORTER_ASSERT(reporter, !path.isLine(NULL));
847 REPORTER_ASSERT(reporter, !path.isLine(pts));
848 // check that pts was untouched
849 REPORTER_ASSERT(reporter, pts[0].equals(value, value));
850 REPORTER_ASSERT(reporter, pts[1].equals(value, value));
851
852 const SkScalar lineX = SkIntToScalar(2);
853 const SkScalar lineY = SkIntToScalar(2);
854 SkASSERT(value != lineX && value != lineY);
855
856 path.lineTo(lineX, lineY);
857 REPORTER_ASSERT(reporter, path.isLine(NULL));
858
859 REPORTER_ASSERT(reporter, !pts[0].equals(moveX, moveY));
860 REPORTER_ASSERT(reporter, !pts[1].equals(lineX, lineY));
861 REPORTER_ASSERT(reporter, path.isLine(pts));
862 REPORTER_ASSERT(reporter, pts[0].equals(moveX, moveY));
863 REPORTER_ASSERT(reporter, pts[1].equals(lineX, lineY));
864
865 path.lineTo(0, 0); // too many points/verbs
866 REPORTER_ASSERT(reporter, !path.isLine(NULL));
867 REPORTER_ASSERT(reporter, !path.isLine(pts));
868 REPORTER_ASSERT(reporter, pts[0].equals(moveX, moveY));
869 REPORTER_ASSERT(reporter, pts[1].equals(lineX, lineY));
870}
871
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000872static void test_conservativelyContains(skiatest::Reporter* reporter) {
873 SkPath path;
874
875 // kBaseRect is used to construct most our test paths: a rect, a circle, and a round-rect.
876 static const SkRect kBaseRect = SkRect::MakeWH(SkIntToScalar(100), SkIntToScalar(100));
877
878 // A circle that bounds kBaseRect (with a significant amount of slop)
879 SkScalar circleR = SkMaxScalar(kBaseRect.width(), kBaseRect.height());
880 circleR = SkScalarMul(circleR, SkFloatToScalar(1.75f)) / 2;
881 static const SkPoint kCircleC = {kBaseRect.centerX(), kBaseRect.centerY()};
882
883 // round-rect radii
884 static const SkScalar kRRRadii[] = {SkIntToScalar(5), SkIntToScalar(3)};
skia.committer@gmail.comcec8de62012-11-14 02:01:22 +0000885
caryclark@google.com56f233a2012-11-19 13:06:06 +0000886 static const struct SUPPRESS_VISIBILITY_WARNING {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000887 SkRect fQueryRect;
888 bool fInRect;
889 bool fInCircle;
890 bool fInRR;
891 } kQueries[] = {
892 {kBaseRect, true, true, false},
893
894 // rect well inside of kBaseRect
895 {SkRect::MakeLTRB(kBaseRect.fLeft + SkFloatToScalar(0.25f)*kBaseRect.width(),
896 kBaseRect.fTop + SkFloatToScalar(0.25f)*kBaseRect.height(),
897 kBaseRect.fRight - SkFloatToScalar(0.25f)*kBaseRect.width(),
898 kBaseRect.fBottom - SkFloatToScalar(0.25f)*kBaseRect.height()),
899 true, true, true},
900
901 // rects with edges off by one from kBaseRect's edges
902 {SkRect::MakeXYWH(kBaseRect.fLeft, kBaseRect.fTop,
903 kBaseRect.width(), kBaseRect.height() + 1),
904 false, true, false},
905 {SkRect::MakeXYWH(kBaseRect.fLeft, kBaseRect.fTop,
906 kBaseRect.width() + 1, kBaseRect.height()),
907 false, true, false},
908 {SkRect::MakeXYWH(kBaseRect.fLeft, kBaseRect.fTop,
909 kBaseRect.width() + 1, kBaseRect.height() + 1),
910 false, true, false},
911 {SkRect::MakeXYWH(kBaseRect.fLeft - 1, kBaseRect.fTop,
912 kBaseRect.width(), kBaseRect.height()),
913 false, true, false},
914 {SkRect::MakeXYWH(kBaseRect.fLeft, kBaseRect.fTop - 1,
915 kBaseRect.width(), kBaseRect.height()),
916 false, true, false},
917 {SkRect::MakeXYWH(kBaseRect.fLeft - 1, kBaseRect.fTop,
918 kBaseRect.width() + 2, kBaseRect.height()),
919 false, true, false},
920 {SkRect::MakeXYWH(kBaseRect.fLeft, kBaseRect.fTop - 1,
921 kBaseRect.width() + 2, kBaseRect.height()),
922 false, true, false},
923
924 // zero-w/h rects at each corner of kBaseRect
925 {SkRect::MakeXYWH(kBaseRect.fLeft, kBaseRect.fTop, 0, 0), true, true, false},
926 {SkRect::MakeXYWH(kBaseRect.fRight, kBaseRect.fTop, 0, 0), true, true, false},
927 {SkRect::MakeXYWH(kBaseRect.fLeft, kBaseRect.fBottom, 0, 0), true, true, false},
928 {SkRect::MakeXYWH(kBaseRect.fRight, kBaseRect.fBottom, 0, 0), true, true, false},
929
930 // far away rect
931 {SkRect::MakeXYWH(10 * kBaseRect.fRight, 10 * kBaseRect.fBottom,
932 SkIntToScalar(10), SkIntToScalar(10)),
933 false, false, false},
934
935 // very large rect containing kBaseRect
936 {SkRect::MakeXYWH(kBaseRect.fLeft - 5 * kBaseRect.width(),
937 kBaseRect.fTop - 5 * kBaseRect.height(),
938 11 * kBaseRect.width(), 11 * kBaseRect.height()),
939 false, false, false},
940
941 // skinny rect that spans same y-range as kBaseRect
942 {SkRect::MakeXYWH(kBaseRect.centerX(), kBaseRect.fTop,
943 SkIntToScalar(1), kBaseRect.height()),
944 true, true, true},
945
946 // short rect that spans same x-range as kBaseRect
947 {SkRect::MakeXYWH(kBaseRect.fLeft, kBaseRect.centerY(), kBaseRect.width(), SkScalar(1)),
948 true, true, true},
949
950 // skinny rect that spans slightly larger y-range than kBaseRect
951 {SkRect::MakeXYWH(kBaseRect.centerX(), kBaseRect.fTop,
952 SkIntToScalar(1), kBaseRect.height() + 1),
953 false, true, false},
954
955 // short rect that spans slightly larger x-range than kBaseRect
956 {SkRect::MakeXYWH(kBaseRect.fLeft, kBaseRect.centerY(),
957 kBaseRect.width() + 1, SkScalar(1)),
958 false, true, false},
959 };
960
961 for (int inv = 0; inv < 4; ++inv) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000962 for (size_t q = 0; q < SK_ARRAY_COUNT(kQueries); ++q) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000963 SkRect qRect = kQueries[q].fQueryRect;
964 if (inv & 0x1) {
965 SkTSwap(qRect.fLeft, qRect.fRight);
966 }
967 if (inv & 0x2) {
968 SkTSwap(qRect.fTop, qRect.fBottom);
969 }
970 for (int d = 0; d < 2; ++d) {
971 SkPath::Direction dir = d ? SkPath::kCCW_Direction : SkPath::kCW_Direction;
972 path.reset();
973 path.addRect(kBaseRect, dir);
974 REPORTER_ASSERT(reporter, kQueries[q].fInRect ==
975 path.conservativelyContainsRect(qRect));
976
977 path.reset();
978 path.addCircle(kCircleC.fX, kCircleC.fY, circleR, dir);
979 REPORTER_ASSERT(reporter, kQueries[q].fInCircle ==
980 path.conservativelyContainsRect(qRect));
981
982 path.reset();
983 path.addRoundRect(kBaseRect, kRRRadii[0], kRRRadii[1], dir);
984 REPORTER_ASSERT(reporter, kQueries[q].fInRR ==
985 path.conservativelyContainsRect(qRect));
986 }
987 // Slightly non-convex shape, shouldn't contain any rects.
988 path.reset();
989 path.moveTo(0, 0);
990 path.lineTo(SkIntToScalar(50), SkFloatToScalar(0.05f));
991 path.lineTo(SkIntToScalar(100), 0);
992 path.lineTo(SkIntToScalar(100), SkIntToScalar(100));
993 path.lineTo(0, SkIntToScalar(100));
994 path.close();
995 REPORTER_ASSERT(reporter, !path.conservativelyContainsRect(qRect));
996 }
997 }
998
999 // make sure a minimal convex shape works, a right tri with edges along pos x and y axes.
1000 path.reset();
1001 path.moveTo(0, 0);
1002 path.lineTo(SkIntToScalar(100), 0);
1003 path.lineTo(0, SkIntToScalar(100));
1004
1005 // inside, on along top edge
1006 REPORTER_ASSERT(reporter, path.conservativelyContainsRect(SkRect::MakeXYWH(SkIntToScalar(50), 0,
1007 SkIntToScalar(10),
1008 SkIntToScalar(10))));
1009 // above
1010 REPORTER_ASSERT(reporter, !path.conservativelyContainsRect(
1011 SkRect::MakeXYWH(SkIntToScalar(50),
1012 SkIntToScalar(-10),
1013 SkIntToScalar(10),
1014 SkIntToScalar(10))));
1015 // to the left
1016 REPORTER_ASSERT(reporter, !path.conservativelyContainsRect(SkRect::MakeXYWH(SkIntToScalar(-10),
1017 SkIntToScalar(5),
1018 SkIntToScalar(5),
1019 SkIntToScalar(5))));
1020
1021 // outside the diagonal edge
1022 REPORTER_ASSERT(reporter, !path.conservativelyContainsRect(SkRect::MakeXYWH(SkIntToScalar(10),
1023 SkIntToScalar(200),
1024 SkIntToScalar(20),
1025 SkIntToScalar(5))));
1026}
1027
caryclark@google.comf1316942011-07-26 19:54:45 +00001028// Simple isRect test is inline TestPath, below.
1029// test_isRect provides more extensive testing.
1030static void test_isRect(skiatest::Reporter* reporter) {
1031 // passing tests (all moveTo / lineTo...
1032 SkPoint r1[] = {{0, 0}, {1, 0}, {1, 1}, {0, 1}};
1033 SkPoint r2[] = {{1, 0}, {1, 1}, {0, 1}, {0, 0}};
1034 SkPoint r3[] = {{1, 1}, {0, 1}, {0, 0}, {1, 0}};
1035 SkPoint r4[] = {{0, 1}, {0, 0}, {1, 0}, {1, 1}};
1036 SkPoint r5[] = {{0, 0}, {0, 1}, {1, 1}, {1, 0}};
1037 SkPoint r6[] = {{0, 1}, {1, 1}, {1, 0}, {0, 0}};
1038 SkPoint r7[] = {{1, 1}, {1, 0}, {0, 0}, {0, 1}};
1039 SkPoint r8[] = {{1, 0}, {0, 0}, {0, 1}, {1, 1}};
1040 SkPoint r9[] = {{0, 1}, {1, 1}, {1, 0}, {0, 0}};
1041 SkPoint ra[] = {{0, 0}, {0, .5f}, {0, 1}, {.5f, 1}, {1, 1}, {1, .5f},
1042 {1, 0}, {.5f, 0}};
1043 SkPoint rb[] = {{0, 0}, {.5f, 0}, {1, 0}, {1, .5f}, {1, 1}, {.5f, 1},
1044 {0, 1}, {0, .5f}};
1045 SkPoint rc[] = {{0, 0}, {1, 0}, {1, 1}, {0, 1}, {0, 0}};
1046 SkPoint rd[] = {{0, 0}, {0, 1}, {1, 1}, {1, 0}, {0, 0}};
1047 SkPoint re[] = {{0, 0}, {1, 0}, {1, 0}, {1, 1}, {0, 1}};
caryclark@google.combfe90372012-11-21 13:56:20 +00001048 SkPoint rf[] = {{1, 0}, {8, 0}, {8, 8}, {0, 8}, {0, 0}};
rmistry@google.comd6176b02012-08-23 18:14:13 +00001049
caryclark@google.comf1316942011-07-26 19:54:45 +00001050 // failing tests
1051 SkPoint f1[] = {{0, 0}, {1, 0}, {1, 1}}; // too few points
1052 SkPoint f2[] = {{0, 0}, {1, 1}, {0, 1}, {1, 0}}; // diagonal
1053 SkPoint f3[] = {{0, 0}, {1, 0}, {1, 1}, {0, 1}, {0, 0}, {1, 0}}; // wraps
1054 SkPoint f4[] = {{0, 0}, {1, 0}, {0, 0}, {1, 0}, {1, 1}, {0, 1}}; // backs up
1055 SkPoint f5[] = {{0, 0}, {1, 0}, {1, 1}, {2, 0}}; // end overshoots
1056 SkPoint f6[] = {{0, 0}, {1, 0}, {1, 1}, {0, 1}, {0, 2}}; // end overshoots
1057 SkPoint f7[] = {{0, 0}, {1, 0}, {1, 1}, {0, 2}}; // end overshoots
1058 SkPoint f8[] = {{0, 0}, {1, 0}, {1, 1}, {1, 0}}; // 'L'
caryclark@google.combfe90372012-11-21 13:56:20 +00001059 SkPoint f9[] = {{1, 0}, {8, 0}, {8, 8}, {0, 8}, {0, 0}, {2, 0}}; // overlaps
1060 SkPoint fa[] = {{1, 0}, {8, 0}, {8, 8}, {0, 8}, {0, -1}, {1, -1}}; // non colinear gap
1061 SkPoint fb[] = {{1, 0}, {8, 0}, {8, 8}, {0, 8}, {0, 1}}; // falls short
rmistry@google.comd6176b02012-08-23 18:14:13 +00001062
caryclark@google.comf1316942011-07-26 19:54:45 +00001063 // failing, no close
1064 SkPoint c1[] = {{0, 0}, {1, 0}, {1, 1}, {0, 1}}; // close doesn't match
1065 SkPoint c2[] = {{0, 0}, {1, 0}, {1, 2}, {0, 2}, {0, 1}}; // ditto
1066
1067 size_t testLen[] = {
1068 sizeof(r1), sizeof(r2), sizeof(r3), sizeof(r4), sizeof(r5), sizeof(r6),
1069 sizeof(r7), sizeof(r8), sizeof(r9), sizeof(ra), sizeof(rb), sizeof(rc),
caryclark@google.combfe90372012-11-21 13:56:20 +00001070 sizeof(rd), sizeof(re), sizeof(rf),
caryclark@google.comf1316942011-07-26 19:54:45 +00001071 sizeof(f1), sizeof(f2), sizeof(f3), sizeof(f4), sizeof(f5), sizeof(f6),
caryclark@google.combfe90372012-11-21 13:56:20 +00001072 sizeof(f7), sizeof(f8), sizeof(f9), sizeof(fa), sizeof(fb),
rmistry@google.comd6176b02012-08-23 18:14:13 +00001073 sizeof(c1), sizeof(c2)
caryclark@google.comf1316942011-07-26 19:54:45 +00001074 };
1075 SkPoint* tests[] = {
caryclark@google.combfe90372012-11-21 13:56:20 +00001076 r1, r2, r3, r4, r5, r6, r7, r8, r9, ra, rb, rc, rd, re, rf,
1077 f1, f2, f3, f4, f5, f6, f7, f8, f9, fa, fb,
rmistry@google.comd6176b02012-08-23 18:14:13 +00001078 c1, c2
caryclark@google.comf1316942011-07-26 19:54:45 +00001079 };
caryclark@google.combfe90372012-11-21 13:56:20 +00001080 SkPoint* lastPass = rf;
1081 SkPoint* lastClose = fb;
caryclark@google.comf1316942011-07-26 19:54:45 +00001082 bool fail = false;
1083 bool close = true;
1084 const size_t testCount = sizeof(tests) / sizeof(tests[0]);
1085 size_t index;
1086 for (size_t testIndex = 0; testIndex < testCount; ++testIndex) {
1087 SkPath path;
1088 path.moveTo(tests[testIndex][0].fX, tests[testIndex][0].fY);
1089 for (index = 1; index < testLen[testIndex] / sizeof(SkPoint); ++index) {
1090 path.lineTo(tests[testIndex][index].fX, tests[testIndex][index].fY);
1091 }
1092 if (close) {
1093 path.close();
1094 }
1095 REPORTER_ASSERT(reporter, fail ^ path.isRect(0));
caryclark@google.comf68154a2012-11-21 15:18:06 +00001096 REPORTER_ASSERT(reporter, fail ^ path.isRect(NULL, NULL));
1097
caryclark@google.com56f233a2012-11-19 13:06:06 +00001098 if (!fail) {
1099 SkRect computed, expected;
1100 expected.set(tests[testIndex], testLen[testIndex] / sizeof(SkPoint));
1101 REPORTER_ASSERT(reporter, path.isRect(&computed));
1102 REPORTER_ASSERT(reporter, expected == computed);
skia.committer@gmail.com1c9c0d32012-11-22 02:02:41 +00001103
caryclark@google.comf68154a2012-11-21 15:18:06 +00001104 bool isClosed;
1105 SkPath::Direction direction, cheapDirection;
1106 REPORTER_ASSERT(reporter, path.cheapComputeDirection(&cheapDirection));
1107 REPORTER_ASSERT(reporter, path.isRect(&isClosed, &direction));
1108 REPORTER_ASSERT(reporter, isClosed == close);
1109 REPORTER_ASSERT(reporter, direction == cheapDirection);
1110 } else {
1111 SkRect computed;
1112 computed.set(123, 456, 789, 1011);
1113 REPORTER_ASSERT(reporter, !path.isRect(&computed));
1114 REPORTER_ASSERT(reporter, computed.fLeft == 123 && computed.fTop == 456);
1115 REPORTER_ASSERT(reporter, computed.fRight == 789 && computed.fBottom == 1011);
1116
1117 bool isClosed = (bool) -1;
1118 SkPath::Direction direction = (SkPath::Direction) -1;
1119 REPORTER_ASSERT(reporter, !path.isRect(&isClosed, &direction));
1120 REPORTER_ASSERT(reporter, isClosed == (bool) -1);
1121 REPORTER_ASSERT(reporter, direction == (SkPath::Direction) -1);
caryclark@google.com56f233a2012-11-19 13:06:06 +00001122 }
skia.committer@gmail.com1c9c0d32012-11-22 02:02:41 +00001123
caryclark@google.comf1316942011-07-26 19:54:45 +00001124 if (tests[testIndex] == lastPass) {
1125 fail = true;
1126 }
1127 if (tests[testIndex] == lastClose) {
1128 close = false;
1129 }
1130 }
rmistry@google.comd6176b02012-08-23 18:14:13 +00001131
caryclark@google.comf1316942011-07-26 19:54:45 +00001132 // fail, close then line
1133 SkPath path1;
1134 path1.moveTo(r1[0].fX, r1[0].fY);
1135 for (index = 1; index < testLen[0] / sizeof(SkPoint); ++index) {
1136 path1.lineTo(r1[index].fX, r1[index].fY);
1137 }
1138 path1.close();
1139 path1.lineTo(1, 0);
1140 REPORTER_ASSERT(reporter, fail ^ path1.isRect(0));
rmistry@google.comd6176b02012-08-23 18:14:13 +00001141
caryclark@google.comf1316942011-07-26 19:54:45 +00001142 // fail, move in the middle
1143 path1.reset();
1144 path1.moveTo(r1[0].fX, r1[0].fY);
1145 for (index = 1; index < testLen[0] / sizeof(SkPoint); ++index) {
1146 if (index == 2) {
1147 path1.moveTo(1, .5f);
1148 }
1149 path1.lineTo(r1[index].fX, r1[index].fY);
1150 }
1151 path1.close();
1152 REPORTER_ASSERT(reporter, fail ^ path1.isRect(0));
1153
1154 // fail, move on the edge
1155 path1.reset();
1156 for (index = 1; index < testLen[0] / sizeof(SkPoint); ++index) {
1157 path1.moveTo(r1[index - 1].fX, r1[index - 1].fY);
1158 path1.lineTo(r1[index].fX, r1[index].fY);
1159 }
1160 path1.close();
1161 REPORTER_ASSERT(reporter, fail ^ path1.isRect(0));
rmistry@google.comd6176b02012-08-23 18:14:13 +00001162
caryclark@google.comf1316942011-07-26 19:54:45 +00001163 // fail, quad
1164 path1.reset();
1165 path1.moveTo(r1[0].fX, r1[0].fY);
1166 for (index = 1; index < testLen[0] / sizeof(SkPoint); ++index) {
1167 if (index == 2) {
1168 path1.quadTo(1, .5f, 1, .5f);
1169 }
1170 path1.lineTo(r1[index].fX, r1[index].fY);
1171 }
1172 path1.close();
1173 REPORTER_ASSERT(reporter, fail ^ path1.isRect(0));
rmistry@google.comd6176b02012-08-23 18:14:13 +00001174
caryclark@google.comf1316942011-07-26 19:54:45 +00001175 // fail, cubic
1176 path1.reset();
1177 path1.moveTo(r1[0].fX, r1[0].fY);
1178 for (index = 1; index < testLen[0] / sizeof(SkPoint); ++index) {
1179 if (index == 2) {
1180 path1.cubicTo(1, .5f, 1, .5f, 1, .5f);
1181 }
1182 path1.lineTo(r1[index].fX, r1[index].fY);
1183 }
1184 path1.close();
1185 REPORTER_ASSERT(reporter, fail ^ path1.isRect(0));
1186}
1187
caryclark@google.com56f233a2012-11-19 13:06:06 +00001188static void test_isNestedRects(skiatest::Reporter* reporter) {
1189 // passing tests (all moveTo / lineTo...
1190 SkPoint r1[] = {{0, 0}, {1, 0}, {1, 1}, {0, 1}};
1191 SkPoint r2[] = {{1, 0}, {1, 1}, {0, 1}, {0, 0}};
1192 SkPoint r3[] = {{1, 1}, {0, 1}, {0, 0}, {1, 0}};
1193 SkPoint r4[] = {{0, 1}, {0, 0}, {1, 0}, {1, 1}};
1194 SkPoint r5[] = {{0, 0}, {0, 1}, {1, 1}, {1, 0}};
1195 SkPoint r6[] = {{0, 1}, {1, 1}, {1, 0}, {0, 0}};
1196 SkPoint r7[] = {{1, 1}, {1, 0}, {0, 0}, {0, 1}};
1197 SkPoint r8[] = {{1, 0}, {0, 0}, {0, 1}, {1, 1}};
1198 SkPoint r9[] = {{0, 1}, {1, 1}, {1, 0}, {0, 0}};
1199 SkPoint ra[] = {{0, 0}, {0, .5f}, {0, 1}, {.5f, 1}, {1, 1}, {1, .5f},
1200 {1, 0}, {.5f, 0}};
1201 SkPoint rb[] = {{0, 0}, {.5f, 0}, {1, 0}, {1, .5f}, {1, 1}, {.5f, 1},
1202 {0, 1}, {0, .5f}};
1203 SkPoint rc[] = {{0, 0}, {1, 0}, {1, 1}, {0, 1}, {0, 0}};
1204 SkPoint rd[] = {{0, 0}, {0, 1}, {1, 1}, {1, 0}, {0, 0}};
1205 SkPoint re[] = {{0, 0}, {1, 0}, {1, 0}, {1, 1}, {0, 1}};
1206
1207 // failing tests
1208 SkPoint f1[] = {{0, 0}, {1, 0}, {1, 1}}; // too few points
1209 SkPoint f2[] = {{0, 0}, {1, 1}, {0, 1}, {1, 0}}; // diagonal
1210 SkPoint f3[] = {{0, 0}, {1, 0}, {1, 1}, {0, 1}, {0, 0}, {1, 0}}; // wraps
1211 SkPoint f4[] = {{0, 0}, {1, 0}, {0, 0}, {1, 0}, {1, 1}, {0, 1}}; // backs up
1212 SkPoint f5[] = {{0, 0}, {1, 0}, {1, 1}, {2, 0}}; // end overshoots
1213 SkPoint f6[] = {{0, 0}, {1, 0}, {1, 1}, {0, 1}, {0, 2}}; // end overshoots
1214 SkPoint f7[] = {{0, 0}, {1, 0}, {1, 1}, {0, 2}}; // end overshoots
1215 SkPoint f8[] = {{0, 0}, {1, 0}, {1, 1}, {1, 0}}; // 'L'
1216
1217 // failing, no close
1218 SkPoint c1[] = {{0, 0}, {1, 0}, {1, 1}, {0, 1}}; // close doesn't match
1219 SkPoint c2[] = {{0, 0}, {1, 0}, {1, 2}, {0, 2}, {0, 1}}; // ditto
1220
1221 size_t testLen[] = {
1222 sizeof(r1), sizeof(r2), sizeof(r3), sizeof(r4), sizeof(r5), sizeof(r6),
1223 sizeof(r7), sizeof(r8), sizeof(r9), sizeof(ra), sizeof(rb), sizeof(rc),
1224 sizeof(rd), sizeof(re),
1225 sizeof(f1), sizeof(f2), sizeof(f3), sizeof(f4), sizeof(f5), sizeof(f6),
1226 sizeof(f7), sizeof(f8),
1227 sizeof(c1), sizeof(c2)
1228 };
1229 SkPoint* tests[] = {
1230 r1, r2, r3, r4, r5, r6, r7, r8, r9, ra, rb, rc, rd, re,
1231 f1, f2, f3, f4, f5, f6, f7, f8,
1232 c1, c2
1233 };
1234 const SkPoint* lastPass = re;
1235 const SkPoint* lastClose = f8;
1236 const size_t testCount = sizeof(tests) / sizeof(tests[0]);
1237 size_t index;
1238 for (int rectFirst = 0; rectFirst <= 1; ++rectFirst) {
1239 bool fail = false;
1240 bool close = true;
1241 for (size_t testIndex = 0; testIndex < testCount; ++testIndex) {
1242 SkPath path;
1243 if (rectFirst) {
1244 path.addRect(-1, -1, 2, 2, SkPath::kCW_Direction);
1245 }
1246 path.moveTo(tests[testIndex][0].fX, tests[testIndex][0].fY);
1247 for (index = 1; index < testLen[testIndex] / sizeof(SkPoint); ++index) {
1248 path.lineTo(tests[testIndex][index].fX, tests[testIndex][index].fY);
1249 }
1250 if (close) {
1251 path.close();
1252 }
1253 if (!rectFirst) {
1254 path.addRect(-1, -1, 2, 2, SkPath::kCCW_Direction);
1255 }
1256 REPORTER_ASSERT(reporter, fail ^ path.isNestedRects(0));
1257 if (!fail) {
1258 SkRect expected[2], computed[2];
1259 SkRect testBounds;
1260 testBounds.set(tests[testIndex], testLen[testIndex] / sizeof(SkPoint));
1261 expected[0] = SkRect::MakeLTRB(-1, -1, 2, 2);
1262 expected[1] = testBounds;
1263 REPORTER_ASSERT(reporter, path.isNestedRects(computed));
1264 REPORTER_ASSERT(reporter, expected[0] == computed[0]);
1265 REPORTER_ASSERT(reporter, expected[1] == computed[1]);
1266 }
1267 if (tests[testIndex] == lastPass) {
1268 fail = true;
1269 }
1270 if (tests[testIndex] == lastClose) {
1271 close = false;
1272 }
1273 }
1274
1275 // fail, close then line
1276 SkPath path1;
1277 if (rectFirst) {
1278 path1.addRect(-1, -1, 2, 2, SkPath::kCW_Direction);
1279 }
1280 path1.moveTo(r1[0].fX, r1[0].fY);
1281 for (index = 1; index < testLen[0] / sizeof(SkPoint); ++index) {
1282 path1.lineTo(r1[index].fX, r1[index].fY);
1283 }
1284 path1.close();
1285 path1.lineTo(1, 0);
1286 if (!rectFirst) {
1287 path1.addRect(-1, -1, 2, 2, SkPath::kCCW_Direction);
1288 }
1289 REPORTER_ASSERT(reporter, fail ^ path1.isNestedRects(0));
1290
1291 // fail, move in the middle
1292 path1.reset();
1293 if (rectFirst) {
1294 path1.addRect(-1, -1, 2, 2, SkPath::kCW_Direction);
1295 }
1296 path1.moveTo(r1[0].fX, r1[0].fY);
1297 for (index = 1; index < testLen[0] / sizeof(SkPoint); ++index) {
1298 if (index == 2) {
1299 path1.moveTo(1, .5f);
1300 }
1301 path1.lineTo(r1[index].fX, r1[index].fY);
1302 }
1303 path1.close();
1304 if (!rectFirst) {
1305 path1.addRect(-1, -1, 2, 2, SkPath::kCCW_Direction);
1306 }
1307 REPORTER_ASSERT(reporter, fail ^ path1.isNestedRects(0));
1308
1309 // fail, move on the edge
1310 path1.reset();
1311 if (rectFirst) {
1312 path1.addRect(-1, -1, 2, 2, SkPath::kCW_Direction);
1313 }
1314 for (index = 1; index < testLen[0] / sizeof(SkPoint); ++index) {
1315 path1.moveTo(r1[index - 1].fX, r1[index - 1].fY);
1316 path1.lineTo(r1[index].fX, r1[index].fY);
1317 }
1318 path1.close();
1319 if (!rectFirst) {
1320 path1.addRect(-1, -1, 2, 2, SkPath::kCCW_Direction);
1321 }
1322 REPORTER_ASSERT(reporter, fail ^ path1.isNestedRects(0));
1323
1324 // fail, quad
1325 path1.reset();
1326 if (rectFirst) {
1327 path1.addRect(-1, -1, 2, 2, SkPath::kCW_Direction);
1328 }
1329 path1.moveTo(r1[0].fX, r1[0].fY);
1330 for (index = 1; index < testLen[0] / sizeof(SkPoint); ++index) {
1331 if (index == 2) {
1332 path1.quadTo(1, .5f, 1, .5f);
1333 }
1334 path1.lineTo(r1[index].fX, r1[index].fY);
1335 }
1336 path1.close();
1337 if (!rectFirst) {
1338 path1.addRect(-1, -1, 2, 2, SkPath::kCCW_Direction);
1339 }
1340 REPORTER_ASSERT(reporter, fail ^ path1.isNestedRects(0));
1341
1342 // fail, cubic
1343 path1.reset();
1344 if (rectFirst) {
1345 path1.addRect(-1, -1, 2, 2, SkPath::kCW_Direction);
1346 }
1347 path1.moveTo(r1[0].fX, r1[0].fY);
1348 for (index = 1; index < testLen[0] / sizeof(SkPoint); ++index) {
1349 if (index == 2) {
1350 path1.cubicTo(1, .5f, 1, .5f, 1, .5f);
1351 }
1352 path1.lineTo(r1[index].fX, r1[index].fY);
1353 }
1354 path1.close();
1355 if (!rectFirst) {
1356 path1.addRect(-1, -1, 2, 2, SkPath::kCCW_Direction);
1357 }
1358 REPORTER_ASSERT(reporter, fail ^ path1.isNestedRects(0));
skia.committer@gmail.com34587162012-11-20 02:01:23 +00001359
caryclark@google.com56f233a2012-11-19 13:06:06 +00001360 // fail, not nested
1361 path1.reset();
1362 path1.addRect(1, 1, 3, 3, SkPath::kCW_Direction);
1363 path1.addRect(2, 2, 4, 4, SkPath::kCW_Direction);
1364 REPORTER_ASSERT(reporter, fail ^ path1.isNestedRects(0));
1365 }
caryclark@google.combfe90372012-11-21 13:56:20 +00001366
1367 // pass, stroke rect
1368 SkPath src, dst;
1369 src.addRect(1, 1, 7, 7, SkPath::kCW_Direction);
1370 SkPaint strokePaint;
1371 strokePaint.setStyle(SkPaint::kStroke_Style);
1372 strokePaint.setStrokeWidth(2);
1373 strokePaint.getFillPath(src, &dst);
1374 REPORTER_ASSERT(reporter, dst.isNestedRects(0));
caryclark@google.com56f233a2012-11-19 13:06:06 +00001375}
1376
robertphillips@google.com2972bb52012-08-07 17:32:51 +00001377static void write_and_read_back(skiatest::Reporter* reporter,
1378 const SkPath& p) {
1379 SkWriter32 writer(100);
1380 writer.writePath(p);
1381 size_t size = writer.size();
1382 SkAutoMalloc storage(size);
1383 writer.flatten(storage.get());
1384 SkReader32 reader(storage.get(), size);
1385
1386 SkPath readBack;
1387 REPORTER_ASSERT(reporter, readBack != p);
1388 reader.readPath(&readBack);
1389 REPORTER_ASSERT(reporter, readBack == p);
1390
rmistry@google.comd6176b02012-08-23 18:14:13 +00001391 REPORTER_ASSERT(reporter, readBack.getConvexityOrUnknown() ==
robertphillips@google.com2972bb52012-08-07 17:32:51 +00001392 p.getConvexityOrUnknown());
1393
1394 REPORTER_ASSERT(reporter, readBack.isOval(NULL) == p.isOval(NULL));
1395
1396 const SkRect& origBounds = p.getBounds();
1397 const SkRect& readBackBounds = readBack.getBounds();
1398
1399 REPORTER_ASSERT(reporter, origBounds == readBackBounds);
1400}
1401
reed@google.com53effc52011-09-21 19:05:12 +00001402static void test_flattening(skiatest::Reporter* reporter) {
1403 SkPath p;
1404
1405 static const SkPoint pts[] = {
1406 { 0, 0 },
1407 { SkIntToScalar(10), SkIntToScalar(10) },
1408 { SkIntToScalar(20), SkIntToScalar(10) }, { SkIntToScalar(20), 0 },
1409 { 0, 0 }, { 0, SkIntToScalar(10) }, { SkIntToScalar(1), SkIntToScalar(10) }
1410 };
1411 p.moveTo(pts[0]);
1412 p.lineTo(pts[1]);
1413 p.quadTo(pts[2], pts[3]);
1414 p.cubicTo(pts[4], pts[5], pts[6]);
1415
robertphillips@google.com2972bb52012-08-07 17:32:51 +00001416 write_and_read_back(reporter, p);
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001417
1418 // create a buffer that should be much larger than the path so we don't
1419 // kill our stack if writer goes too far.
1420 char buffer[1024];
1421 uint32_t size1 = p.writeToMemory(NULL);
1422 uint32_t size2 = p.writeToMemory(buffer);
1423 REPORTER_ASSERT(reporter, size1 == size2);
1424
1425 SkPath p2;
1426 uint32_t size3 = p2.readFromMemory(buffer);
1427 REPORTER_ASSERT(reporter, size1 == size3);
1428 REPORTER_ASSERT(reporter, p == p2);
1429
1430 char buffer2[1024];
1431 size3 = p2.writeToMemory(buffer2);
1432 REPORTER_ASSERT(reporter, size1 == size3);
1433 REPORTER_ASSERT(reporter, memcmp(buffer, buffer2, size1) == 0);
robertphillips@google.com2972bb52012-08-07 17:32:51 +00001434
1435 // test persistence of the oval flag & convexity
1436 {
1437 SkPath oval;
1438 SkRect rect = SkRect::MakeWH(10, 10);
1439 oval.addOval(rect);
1440
1441 write_and_read_back(reporter, oval);
1442 }
reed@google.com53effc52011-09-21 19:05:12 +00001443}
1444
1445static void test_transform(skiatest::Reporter* reporter) {
1446 SkPath p, p1;
rmistry@google.comd6176b02012-08-23 18:14:13 +00001447
reed@google.com53effc52011-09-21 19:05:12 +00001448 static const SkPoint pts[] = {
1449 { 0, 0 },
1450 { SkIntToScalar(10), SkIntToScalar(10) },
1451 { SkIntToScalar(20), SkIntToScalar(10) }, { SkIntToScalar(20), 0 },
1452 { 0, 0 }, { 0, SkIntToScalar(10) }, { SkIntToScalar(1), SkIntToScalar(10) }
1453 };
1454 p.moveTo(pts[0]);
1455 p.lineTo(pts[1]);
1456 p.quadTo(pts[2], pts[3]);
1457 p.cubicTo(pts[4], pts[5], pts[6]);
rmistry@google.comd6176b02012-08-23 18:14:13 +00001458
reed@google.com53effc52011-09-21 19:05:12 +00001459 SkMatrix matrix;
1460 matrix.reset();
1461 p.transform(matrix, &p1);
1462 REPORTER_ASSERT(reporter, p == p1);
rmistry@google.comd6176b02012-08-23 18:14:13 +00001463
reed@google.com53effc52011-09-21 19:05:12 +00001464 matrix.setScale(SK_Scalar1 * 2, SK_Scalar1 * 3);
1465 p.transform(matrix, &p1);
1466 SkPoint pts1[7];
1467 int count = p1.getPoints(pts1, 7);
1468 REPORTER_ASSERT(reporter, 7 == count);
1469 for (int i = 0; i < count; ++i) {
1470 SkPoint newPt = SkPoint::Make(pts[i].fX * 2, pts[i].fY * 3);
1471 REPORTER_ASSERT(reporter, newPt == pts1[i]);
1472 }
1473}
1474
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001475static void test_zero_length_paths(skiatest::Reporter* reporter) {
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001476 SkPath p;
schenney@chromium.org7e963602012-06-13 17:05:43 +00001477 uint8_t verbs[32];
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001478
caryclark@google.com56f233a2012-11-19 13:06:06 +00001479 struct SUPPRESS_VISIBILITY_WARNING zeroPathTestData {
schenney@chromium.org7e963602012-06-13 17:05:43 +00001480 const char* testPath;
1481 const size_t numResultPts;
1482 const SkRect resultBound;
1483 const SkPath::Verb* resultVerbs;
1484 const size_t numResultVerbs;
1485 };
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001486
schenney@chromium.org7e963602012-06-13 17:05:43 +00001487 static const SkPath::Verb resultVerbs1[] = { SkPath::kMove_Verb };
1488 static const SkPath::Verb resultVerbs2[] = { SkPath::kMove_Verb, SkPath::kMove_Verb };
1489 static const SkPath::Verb resultVerbs3[] = { SkPath::kMove_Verb, SkPath::kClose_Verb };
1490 static const SkPath::Verb resultVerbs4[] = { SkPath::kMove_Verb, SkPath::kClose_Verb, SkPath::kMove_Verb, SkPath::kClose_Verb };
1491 static const SkPath::Verb resultVerbs5[] = { SkPath::kMove_Verb, SkPath::kLine_Verb };
1492 static const SkPath::Verb resultVerbs6[] = { SkPath::kMove_Verb, SkPath::kLine_Verb, SkPath::kMove_Verb, SkPath::kLine_Verb };
1493 static const SkPath::Verb resultVerbs7[] = { SkPath::kMove_Verb, SkPath::kLine_Verb, SkPath::kClose_Verb };
1494 static const SkPath::Verb resultVerbs8[] = {
1495 SkPath::kMove_Verb, SkPath::kLine_Verb, SkPath::kClose_Verb, SkPath::kMove_Verb, SkPath::kLine_Verb, SkPath::kClose_Verb
1496 };
1497 static const SkPath::Verb resultVerbs9[] = { SkPath::kMove_Verb, SkPath::kQuad_Verb };
1498 static const SkPath::Verb resultVerbs10[] = { SkPath::kMove_Verb, SkPath::kQuad_Verb, SkPath::kMove_Verb, SkPath::kQuad_Verb };
1499 static const SkPath::Verb resultVerbs11[] = { SkPath::kMove_Verb, SkPath::kQuad_Verb, SkPath::kClose_Verb };
1500 static const SkPath::Verb resultVerbs12[] = {
1501 SkPath::kMove_Verb, SkPath::kQuad_Verb, SkPath::kClose_Verb, SkPath::kMove_Verb, SkPath::kQuad_Verb, SkPath::kClose_Verb
1502 };
1503 static const SkPath::Verb resultVerbs13[] = { SkPath::kMove_Verb, SkPath::kCubic_Verb };
1504 static const SkPath::Verb resultVerbs14[] = { SkPath::kMove_Verb, SkPath::kCubic_Verb, SkPath::kMove_Verb, SkPath::kCubic_Verb };
1505 static const SkPath::Verb resultVerbs15[] = { SkPath::kMove_Verb, SkPath::kCubic_Verb, SkPath::kClose_Verb };
1506 static const SkPath::Verb resultVerbs16[] = {
1507 SkPath::kMove_Verb, SkPath::kCubic_Verb, SkPath::kClose_Verb, SkPath::kMove_Verb, SkPath::kCubic_Verb, SkPath::kClose_Verb
1508 };
1509 static const struct zeroPathTestData gZeroLengthTests[] = {
1510 { "M 1 1", 1, {0, 0, 0, 0}, resultVerbs1, SK_ARRAY_COUNT(resultVerbs1) },
schenney@chromium.orgaaf16882012-06-13 17:41:00 +00001511 { "M 1 1 M 2 1", 2, {SK_Scalar1, SK_Scalar1, 2*SK_Scalar1, SK_Scalar1}, resultVerbs2, SK_ARRAY_COUNT(resultVerbs2) },
schenney@chromium.org7e963602012-06-13 17:05:43 +00001512 { "M 1 1 z", 1, {0, 0, 0, 0}, resultVerbs3, SK_ARRAY_COUNT(resultVerbs3) },
schenney@chromium.orgaaf16882012-06-13 17:41:00 +00001513 { "M 1 1 z M 2 1 z", 2, {SK_Scalar1, SK_Scalar1, 2*SK_Scalar1, SK_Scalar1}, resultVerbs4, SK_ARRAY_COUNT(resultVerbs4) },
1514 { "M 1 1 L 1 1", 2, {SK_Scalar1, SK_Scalar1, SK_Scalar1, SK_Scalar1}, resultVerbs5, SK_ARRAY_COUNT(resultVerbs5) },
1515 { "M 1 1 L 1 1 M 2 1 L 2 1", 4, {SK_Scalar1, SK_Scalar1, 2*SK_Scalar1, SK_Scalar1}, resultVerbs6, SK_ARRAY_COUNT(resultVerbs6) },
1516 { "M 1 1 L 1 1 z", 2, {SK_Scalar1, SK_Scalar1, SK_Scalar1, SK_Scalar1}, resultVerbs7, SK_ARRAY_COUNT(resultVerbs7) },
1517 { "M 1 1 L 1 1 z M 2 1 L 2 1 z", 4, {SK_Scalar1, SK_Scalar1, 2*SK_Scalar1, SK_Scalar1}, resultVerbs8, SK_ARRAY_COUNT(resultVerbs8) },
1518 { "M 1 1 Q 1 1 1 1", 3, {SK_Scalar1, SK_Scalar1, SK_Scalar1, SK_Scalar1}, resultVerbs9, SK_ARRAY_COUNT(resultVerbs9) },
1519 { "M 1 1 Q 1 1 1 1 M 2 1 Q 2 1 2 1", 6, {SK_Scalar1, SK_Scalar1, 2*SK_Scalar1, SK_Scalar1}, resultVerbs10, SK_ARRAY_COUNT(resultVerbs10) },
1520 { "M 1 1 Q 1 1 1 1 z", 3, {SK_Scalar1, SK_Scalar1, SK_Scalar1, SK_Scalar1}, resultVerbs11, SK_ARRAY_COUNT(resultVerbs11) },
1521 { "M 1 1 Q 1 1 1 1 z M 2 1 Q 2 1 2 1 z", 6, {SK_Scalar1, SK_Scalar1, 2*SK_Scalar1, SK_Scalar1}, resultVerbs12, SK_ARRAY_COUNT(resultVerbs12) },
1522 { "M 1 1 C 1 1 1 1 1 1", 4, {SK_Scalar1, SK_Scalar1, SK_Scalar1, SK_Scalar1}, resultVerbs13, SK_ARRAY_COUNT(resultVerbs13) },
1523 { "M 1 1 C 1 1 1 1 1 1 M 2 1 C 2 1 2 1 2 1", 8, {SK_Scalar1, SK_Scalar1, 2*SK_Scalar1, SK_Scalar1}, resultVerbs14,
schenney@chromium.org7e963602012-06-13 17:05:43 +00001524 SK_ARRAY_COUNT(resultVerbs14)
1525 },
schenney@chromium.orgaaf16882012-06-13 17:41:00 +00001526 { "M 1 1 C 1 1 1 1 1 1 z", 4, {SK_Scalar1, SK_Scalar1, SK_Scalar1, SK_Scalar1}, resultVerbs15, SK_ARRAY_COUNT(resultVerbs15) },
1527 { "M 1 1 C 1 1 1 1 1 1 z M 2 1 C 2 1 2 1 2 1 z", 8, {SK_Scalar1, SK_Scalar1, 2*SK_Scalar1, SK_Scalar1}, resultVerbs16,
schenney@chromium.org7e963602012-06-13 17:05:43 +00001528 SK_ARRAY_COUNT(resultVerbs16)
1529 }
1530 };
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001531
schenney@chromium.org7e963602012-06-13 17:05:43 +00001532 for (size_t i = 0; i < SK_ARRAY_COUNT(gZeroLengthTests); ++i) {
1533 p.reset();
1534 bool valid = SkParsePath::FromSVGString(gZeroLengthTests[i].testPath, &p);
1535 REPORTER_ASSERT(reporter, valid);
1536 REPORTER_ASSERT(reporter, !p.isEmpty());
1537 REPORTER_ASSERT(reporter, gZeroLengthTests[i].numResultPts == (size_t)p.countPoints());
1538 REPORTER_ASSERT(reporter, gZeroLengthTests[i].resultBound == p.getBounds());
1539 REPORTER_ASSERT(reporter, gZeroLengthTests[i].numResultVerbs == (size_t)p.getVerbs(verbs, SK_ARRAY_COUNT(verbs)));
1540 for (size_t j = 0; j < gZeroLengthTests[i].numResultVerbs; ++j) {
1541 REPORTER_ASSERT(reporter, gZeroLengthTests[i].resultVerbs[j] == verbs[j]);
1542 }
bsalomon@google.comdf9d6562012-06-07 21:43:15 +00001543 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001544}
1545
1546struct SegmentInfo {
1547 SkPath fPath;
1548 int fPointCount;
1549};
1550
reed@google.com10296cc2011-09-21 12:29:05 +00001551#define kCurveSegmentMask (SkPath::kQuad_SegmentMask | SkPath::kCubic_SegmentMask)
1552
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001553static void test_segment_masks(skiatest::Reporter* reporter) {
reed@google.comeef938c2012-08-01 20:01:49 +00001554 SkPath p, p2;
1555
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001556 p.moveTo(0, 0);
1557 p.quadTo(100, 100, 200, 200);
1558 REPORTER_ASSERT(reporter, SkPath::kQuad_SegmentMask == p.getSegmentMasks());
1559 REPORTER_ASSERT(reporter, !p.isEmpty());
reed@google.comeef938c2012-08-01 20:01:49 +00001560 p2 = p;
1561 REPORTER_ASSERT(reporter, p2.getSegmentMasks() == p.getSegmentMasks());
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001562 p.cubicTo(100, 100, 200, 200, 300, 300);
1563 REPORTER_ASSERT(reporter, kCurveSegmentMask == p.getSegmentMasks());
1564 REPORTER_ASSERT(reporter, !p.isEmpty());
reed@google.comeef938c2012-08-01 20:01:49 +00001565 p2 = p;
1566 REPORTER_ASSERT(reporter, p2.getSegmentMasks() == p.getSegmentMasks());
1567
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001568 p.reset();
1569 p.moveTo(0, 0);
1570 p.cubicTo(100, 100, 200, 200, 300, 300);
1571 REPORTER_ASSERT(reporter, SkPath::kCubic_SegmentMask == p.getSegmentMasks());
reed@google.comeef938c2012-08-01 20:01:49 +00001572 p2 = p;
1573 REPORTER_ASSERT(reporter, p2.getSegmentMasks() == p.getSegmentMasks());
rmistry@google.comd6176b02012-08-23 18:14:13 +00001574
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001575 REPORTER_ASSERT(reporter, !p.isEmpty());
1576}
1577
1578static void test_iter(skiatest::Reporter* reporter) {
schenney@chromium.org7e963602012-06-13 17:05:43 +00001579 SkPath p;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001580 SkPoint pts[4];
1581
1582 // Test an iterator with no path
1583 SkPath::Iter noPathIter;
1584 REPORTER_ASSERT(reporter, noPathIter.next(pts) == SkPath::kDone_Verb);
schenney@chromium.org7e963602012-06-13 17:05:43 +00001585
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001586 // Test that setting an empty path works
1587 noPathIter.setPath(p, false);
1588 REPORTER_ASSERT(reporter, noPathIter.next(pts) == SkPath::kDone_Verb);
schenney@chromium.org7e963602012-06-13 17:05:43 +00001589
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001590 // Test that close path makes no difference for an empty path
1591 noPathIter.setPath(p, true);
1592 REPORTER_ASSERT(reporter, noPathIter.next(pts) == SkPath::kDone_Verb);
schenney@chromium.org7e963602012-06-13 17:05:43 +00001593
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001594 // Test an iterator with an initial empty path
1595 SkPath::Iter iter(p, false);
1596 REPORTER_ASSERT(reporter, iter.next(pts) == SkPath::kDone_Verb);
1597
1598 // Test that close path makes no difference
schenney@chromium.org7e963602012-06-13 17:05:43 +00001599 iter.setPath(p, true);
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001600 REPORTER_ASSERT(reporter, iter.next(pts) == SkPath::kDone_Verb);
1601
rmistry@google.comd6176b02012-08-23 18:14:13 +00001602
schenney@chromium.org7e963602012-06-13 17:05:43 +00001603 struct iterTestData {
1604 const char* testPath;
1605 const bool forceClose;
1606 const bool consumeDegenerates;
1607 const size_t* numResultPtsPerVerb;
1608 const SkPoint* resultPts;
1609 const SkPath::Verb* resultVerbs;
1610 const size_t numResultVerbs;
1611 };
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001612
schenney@chromium.org7e963602012-06-13 17:05:43 +00001613 static const SkPath::Verb resultVerbs1[] = { SkPath::kDone_Verb };
1614 static const SkPath::Verb resultVerbs2[] = {
1615 SkPath::kMove_Verb, SkPath::kLine_Verb, SkPath::kLine_Verb, SkPath::kDone_Verb
1616 };
1617 static const SkPath::Verb resultVerbs3[] = {
1618 SkPath::kMove_Verb, SkPath::kLine_Verb, SkPath::kLine_Verb, SkPath::kLine_Verb, SkPath::kClose_Verb, SkPath::kDone_Verb
1619 };
1620 static const SkPath::Verb resultVerbs4[] = {
1621 SkPath::kMove_Verb, SkPath::kLine_Verb, SkPath::kMove_Verb, SkPath::kClose_Verb, SkPath::kDone_Verb
1622 };
1623 static const SkPath::Verb resultVerbs5[] = {
1624 SkPath::kMove_Verb, SkPath::kLine_Verb, SkPath::kClose_Verb, SkPath::kMove_Verb, SkPath::kClose_Verb, SkPath::kDone_Verb
1625 };
1626 static const size_t resultPtsSizes1[] = { 0 };
schenney@chromium.orgfedd09b2012-06-13 18:29:20 +00001627 static const size_t resultPtsSizes2[] = { 1, 2, 2, 0 };
1628 static const size_t resultPtsSizes3[] = { 1, 2, 2, 2, 1, 0 };
1629 static const size_t resultPtsSizes4[] = { 1, 2, 1, 1, 0 };
1630 static const size_t resultPtsSizes5[] = { 1, 2, 1, 1, 1, 0 };
schenney@chromium.orgaaf16882012-06-13 17:41:00 +00001631 static const SkPoint* resultPts1 = 0;
schenney@chromium.org7e963602012-06-13 17:05:43 +00001632 static const SkPoint resultPts2[] = {
1633 { SK_Scalar1, 0 }, { SK_Scalar1, 0 }, { SK_Scalar1, SK_Scalar1 }, { SK_Scalar1, SK_Scalar1 }, { 0, SK_Scalar1 }
1634 };
1635 static const SkPoint resultPts3[] = {
1636 { SK_Scalar1, 0 }, { SK_Scalar1, 0 }, { SK_Scalar1, SK_Scalar1 }, { SK_Scalar1, SK_Scalar1 }, { 0, SK_Scalar1 },
1637 { 0, SK_Scalar1 }, { SK_Scalar1, 0 }, { SK_Scalar1, 0 }
1638 };
1639 static const SkPoint resultPts4[] = {
1640 { SK_Scalar1, 0 }, { SK_Scalar1, 0 }, { SK_Scalar1, 0 }, { 0, 0 }, { 0, 0 }
1641 };
1642 static const SkPoint resultPts5[] = {
1643 { SK_Scalar1, 0 }, { SK_Scalar1, 0 }, { SK_Scalar1, 0 }, { SK_Scalar1, 0 }, { 0, 0 }, { 0, 0 }
1644 };
1645 static const struct iterTestData gIterTests[] = {
1646 { "M 1 0", false, true, resultPtsSizes1, resultPts1, resultVerbs1, SK_ARRAY_COUNT(resultVerbs1) },
schenney@chromium.orgaaf16882012-06-13 17:41:00 +00001647 { "M 1 0 M 2 0 M 3 0 M 4 0 M 5 0", false, true, resultPtsSizes1, resultPts1, resultVerbs1, SK_ARRAY_COUNT(resultVerbs1) },
1648 { "M 1 0 M 1 0 M 3 0 M 4 0 M 5 0", true, true, resultPtsSizes1, resultPts1, resultVerbs1, SK_ARRAY_COUNT(resultVerbs1) },
schenney@chromium.org7e963602012-06-13 17:05:43 +00001649 { "z", false, true, resultPtsSizes1, resultPts1, resultVerbs1, SK_ARRAY_COUNT(resultVerbs1) },
1650 { "z", true, true, resultPtsSizes1, resultPts1, resultVerbs1, SK_ARRAY_COUNT(resultVerbs1) },
1651 { "z M 1 0 z z M 2 0 z M 3 0 M 4 0 z", false, true, resultPtsSizes1, resultPts1, resultVerbs1, SK_ARRAY_COUNT(resultVerbs1) },
1652 { "z M 1 0 z z M 2 0 z M 3 0 M 4 0 z", true, true, resultPtsSizes1, resultPts1, resultVerbs1, SK_ARRAY_COUNT(resultVerbs1) },
schenney@chromium.orgaaf16882012-06-13 17:41:00 +00001653 { "M 1 0 L 1 1 L 0 1 M 0 0 z", false, true, resultPtsSizes2, resultPts2, resultVerbs2, SK_ARRAY_COUNT(resultVerbs2) },
1654 { "M 1 0 L 1 1 L 0 1 M 0 0 z", true, true, resultPtsSizes3, resultPts3, resultVerbs3, SK_ARRAY_COUNT(resultVerbs3) },
1655 { "M 1 0 L 1 0 M 0 0 z", false, true, resultPtsSizes1, resultPts1, resultVerbs1, SK_ARRAY_COUNT(resultVerbs1) },
1656 { "M 1 0 L 1 0 M 0 0 z", true, true, resultPtsSizes1, resultPts1, resultVerbs1, SK_ARRAY_COUNT(resultVerbs1) },
1657 { "M 1 0 L 1 0 M 0 0 z", false, false, resultPtsSizes4, resultPts4, resultVerbs4, SK_ARRAY_COUNT(resultVerbs4) },
1658 { "M 1 0 L 1 0 M 0 0 z", true, false, resultPtsSizes5, resultPts5, resultVerbs5, SK_ARRAY_COUNT(resultVerbs5) }
schenney@chromium.org7e963602012-06-13 17:05:43 +00001659 };
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001660
schenney@chromium.org7e963602012-06-13 17:05:43 +00001661 for (size_t i = 0; i < SK_ARRAY_COUNT(gIterTests); ++i) {
1662 p.reset();
1663 bool valid = SkParsePath::FromSVGString(gIterTests[i].testPath, &p);
1664 REPORTER_ASSERT(reporter, valid);
1665 iter.setPath(p, gIterTests[i].forceClose);
1666 int j = 0, l = 0;
1667 do {
1668 REPORTER_ASSERT(reporter, iter.next(pts, gIterTests[i].consumeDegenerates) == gIterTests[i].resultVerbs[j]);
1669 for (int k = 0; k < (int)gIterTests[i].numResultPtsPerVerb[j]; ++k) {
1670 REPORTER_ASSERT(reporter, pts[k] == gIterTests[i].resultPts[l++]);
1671 }
1672 } while (gIterTests[i].resultVerbs[j++] != SkPath::kDone_Verb);
1673 REPORTER_ASSERT(reporter, j == (int)gIterTests[i].numResultVerbs);
1674 }
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001675
1676 // The GM degeneratesegments.cpp test is more extensive
1677}
1678
1679static void test_raw_iter(skiatest::Reporter* reporter) {
1680 SkPath p;
1681 SkPoint pts[4];
1682
1683 // Test an iterator with no path
1684 SkPath::RawIter noPathIter;
1685 REPORTER_ASSERT(reporter, noPathIter.next(pts) == SkPath::kDone_Verb);
1686 // Test that setting an empty path works
1687 noPathIter.setPath(p);
1688 REPORTER_ASSERT(reporter, noPathIter.next(pts) == SkPath::kDone_Verb);
rmistry@google.comd6176b02012-08-23 18:14:13 +00001689
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001690 // Test an iterator with an initial empty path
1691 SkPath::RawIter iter(p);
1692 REPORTER_ASSERT(reporter, iter.next(pts) == SkPath::kDone_Verb);
1693
1694 // Test that a move-only path returns the move.
1695 p.moveTo(SK_Scalar1, 0);
1696 iter.setPath(p);
1697 REPORTER_ASSERT(reporter, iter.next(pts) == SkPath::kMove_Verb);
1698 REPORTER_ASSERT(reporter, pts[0].fX == SK_Scalar1);
1699 REPORTER_ASSERT(reporter, pts[0].fY == 0);
1700 REPORTER_ASSERT(reporter, iter.next(pts) == SkPath::kDone_Verb);
1701
1702 // No matter how many moves we add, we should get them all back
1703 p.moveTo(SK_Scalar1*2, SK_Scalar1);
1704 p.moveTo(SK_Scalar1*3, SK_Scalar1*2);
1705 iter.setPath(p);
1706 REPORTER_ASSERT(reporter, iter.next(pts) == SkPath::kMove_Verb);
1707 REPORTER_ASSERT(reporter, pts[0].fX == SK_Scalar1);
1708 REPORTER_ASSERT(reporter, pts[0].fY == 0);
1709 REPORTER_ASSERT(reporter, iter.next(pts) == SkPath::kMove_Verb);
1710 REPORTER_ASSERT(reporter, pts[0].fX == SK_Scalar1*2);
1711 REPORTER_ASSERT(reporter, pts[0].fY == SK_Scalar1);
1712 REPORTER_ASSERT(reporter, iter.next(pts) == SkPath::kMove_Verb);
1713 REPORTER_ASSERT(reporter, pts[0].fX == SK_Scalar1*3);
1714 REPORTER_ASSERT(reporter, pts[0].fY == SK_Scalar1*2);
1715 REPORTER_ASSERT(reporter, iter.next(pts) == SkPath::kDone_Verb);
1716
1717 // Initial close is never ever stored
1718 p.reset();
1719 p.close();
1720 iter.setPath(p);
1721 REPORTER_ASSERT(reporter, iter.next(pts) == SkPath::kDone_Verb);
1722
1723 // Move/close sequences
1724 p.reset();
1725 p.close(); // Not stored, no purpose
1726 p.moveTo(SK_Scalar1, 0);
1727 p.close();
1728 p.close(); // Not stored, no purpose
1729 p.moveTo(SK_Scalar1*2, SK_Scalar1);
1730 p.close();
1731 p.moveTo(SK_Scalar1*3, SK_Scalar1*2);
1732 p.moveTo(SK_Scalar1*4, SK_Scalar1*3);
1733 p.close();
1734 iter.setPath(p);
1735 REPORTER_ASSERT(reporter, iter.next(pts) == SkPath::kMove_Verb);
1736 REPORTER_ASSERT(reporter, pts[0].fX == SK_Scalar1);
1737 REPORTER_ASSERT(reporter, pts[0].fY == 0);
1738 REPORTER_ASSERT(reporter, iter.next(pts) == SkPath::kClose_Verb);
1739 REPORTER_ASSERT(reporter, pts[0].fX == SK_Scalar1);
1740 REPORTER_ASSERT(reporter, pts[0].fY == 0);
1741 REPORTER_ASSERT(reporter, iter.next(pts) == SkPath::kMove_Verb);
1742 REPORTER_ASSERT(reporter, pts[0].fX == SK_Scalar1*2);
1743 REPORTER_ASSERT(reporter, pts[0].fY == SK_Scalar1);
1744 REPORTER_ASSERT(reporter, iter.next(pts) == SkPath::kClose_Verb);
1745 REPORTER_ASSERT(reporter, pts[0].fX == SK_Scalar1*2);
1746 REPORTER_ASSERT(reporter, pts[0].fY == SK_Scalar1);
1747 REPORTER_ASSERT(reporter, iter.next(pts) == SkPath::kMove_Verb);
1748 REPORTER_ASSERT(reporter, pts[0].fX == SK_Scalar1*3);
1749 REPORTER_ASSERT(reporter, pts[0].fY == SK_Scalar1*2);
1750 REPORTER_ASSERT(reporter, iter.next(pts) == SkPath::kMove_Verb);
1751 REPORTER_ASSERT(reporter, pts[0].fX == SK_Scalar1*4);
1752 REPORTER_ASSERT(reporter, pts[0].fY == SK_Scalar1*3);
1753 REPORTER_ASSERT(reporter, iter.next(pts) == SkPath::kClose_Verb);
1754 REPORTER_ASSERT(reporter, pts[0].fX == SK_Scalar1*4);
1755 REPORTER_ASSERT(reporter, pts[0].fY == SK_Scalar1*3);
1756 REPORTER_ASSERT(reporter, iter.next(pts) == SkPath::kDone_Verb);
1757
1758 // Generate random paths and verify
1759 SkPoint randomPts[25];
1760 for (int i = 0; i < 5; ++i) {
1761 for (int j = 0; j < 5; ++j) {
1762 randomPts[i*5+j].set(SK_Scalar1*i, SK_Scalar1*j);
1763 }
1764 }
1765
1766 // Max of 10 segments, max 3 points per segment
1767 SkRandom rand(9876543);
1768 SkPoint expectedPts[31]; // May have leading moveTo
reed@google.comd335d1d2012-01-12 18:17:11 +00001769 SkPath::Verb expectedVerbs[22]; // May have leading moveTo
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001770 SkPath::Verb nextVerb;
reed@google.comd335d1d2012-01-12 18:17:11 +00001771
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001772 for (int i = 0; i < 500; ++i) {
1773 p.reset();
1774 bool lastWasClose = true;
1775 bool haveMoveTo = false;
reed@google.comd335d1d2012-01-12 18:17:11 +00001776 SkPoint lastMoveToPt = { 0, 0 };
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001777 int numPoints = 0;
1778 int numVerbs = (rand.nextU() >> 16) % 10;
1779 int numIterVerbs = 0;
1780 for (int j = 0; j < numVerbs; ++j) {
1781 do {
1782 nextVerb = static_cast<SkPath::Verb>((rand.nextU() >> 16) % SkPath::kDone_Verb);
1783 } while (lastWasClose && nextVerb == SkPath::kClose_Verb);
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001784 switch (nextVerb) {
1785 case SkPath::kMove_Verb:
1786 expectedPts[numPoints] = randomPts[(rand.nextU() >> 16) % 25];
1787 p.moveTo(expectedPts[numPoints]);
reed@google.comd335d1d2012-01-12 18:17:11 +00001788 lastMoveToPt = expectedPts[numPoints];
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001789 numPoints += 1;
1790 lastWasClose = false;
1791 haveMoveTo = true;
1792 break;
1793 case SkPath::kLine_Verb:
1794 if (!haveMoveTo) {
reed@google.comd335d1d2012-01-12 18:17:11 +00001795 expectedPts[numPoints++] = lastMoveToPt;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001796 expectedVerbs[numIterVerbs++] = SkPath::kMove_Verb;
1797 haveMoveTo = true;
1798 }
1799 expectedPts[numPoints] = randomPts[(rand.nextU() >> 16) % 25];
1800 p.lineTo(expectedPts[numPoints]);
1801 numPoints += 1;
1802 lastWasClose = false;
1803 break;
1804 case SkPath::kQuad_Verb:
1805 if (!haveMoveTo) {
reed@google.comd335d1d2012-01-12 18:17:11 +00001806 expectedPts[numPoints++] = lastMoveToPt;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001807 expectedVerbs[numIterVerbs++] = SkPath::kMove_Verb;
1808 haveMoveTo = true;
1809 }
1810 expectedPts[numPoints] = randomPts[(rand.nextU() >> 16) % 25];
1811 expectedPts[numPoints + 1] = randomPts[(rand.nextU() >> 16) % 25];
1812 p.quadTo(expectedPts[numPoints], expectedPts[numPoints + 1]);
1813 numPoints += 2;
1814 lastWasClose = false;
1815 break;
1816 case SkPath::kCubic_Verb:
1817 if (!haveMoveTo) {
reed@google.comd335d1d2012-01-12 18:17:11 +00001818 expectedPts[numPoints++] = lastMoveToPt;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001819 expectedVerbs[numIterVerbs++] = SkPath::kMove_Verb;
1820 haveMoveTo = true;
1821 }
1822 expectedPts[numPoints] = randomPts[(rand.nextU() >> 16) % 25];
1823 expectedPts[numPoints + 1] = randomPts[(rand.nextU() >> 16) % 25];
1824 expectedPts[numPoints + 2] = randomPts[(rand.nextU() >> 16) % 25];
1825 p.cubicTo(expectedPts[numPoints], expectedPts[numPoints + 1],
1826 expectedPts[numPoints + 2]);
1827 numPoints += 3;
1828 lastWasClose = false;
1829 break;
1830 case SkPath::kClose_Verb:
1831 p.close();
reed@google.comd335d1d2012-01-12 18:17:11 +00001832 haveMoveTo = false;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001833 lastWasClose = true;
1834 break;
1835 default:;
1836 }
1837 expectedVerbs[numIterVerbs++] = nextVerb;
1838 }
rmistry@google.comd6176b02012-08-23 18:14:13 +00001839
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001840 iter.setPath(p);
1841 numVerbs = numIterVerbs;
1842 numIterVerbs = 0;
1843 int numIterPts = 0;
1844 SkPoint lastMoveTo;
1845 SkPoint lastPt;
1846 lastMoveTo.set(0, 0);
1847 lastPt.set(0, 0);
1848 while ((nextVerb = iter.next(pts)) != SkPath::kDone_Verb) {
1849 REPORTER_ASSERT(reporter, nextVerb == expectedVerbs[numIterVerbs]);
1850 numIterVerbs++;
1851 switch (nextVerb) {
1852 case SkPath::kMove_Verb:
1853 REPORTER_ASSERT(reporter, numIterPts < numPoints);
1854 REPORTER_ASSERT(reporter, pts[0] == expectedPts[numIterPts]);
1855 lastPt = lastMoveTo = pts[0];
1856 numIterPts += 1;
1857 break;
1858 case SkPath::kLine_Verb:
1859 REPORTER_ASSERT(reporter, numIterPts < numPoints + 1);
1860 REPORTER_ASSERT(reporter, pts[0] == lastPt);
1861 REPORTER_ASSERT(reporter, pts[1] == expectedPts[numIterPts]);
1862 lastPt = pts[1];
1863 numIterPts += 1;
1864 break;
1865 case SkPath::kQuad_Verb:
1866 REPORTER_ASSERT(reporter, numIterPts < numPoints + 2);
1867 REPORTER_ASSERT(reporter, pts[0] == lastPt);
1868 REPORTER_ASSERT(reporter, pts[1] == expectedPts[numIterPts]);
1869 REPORTER_ASSERT(reporter, pts[2] == expectedPts[numIterPts + 1]);
1870 lastPt = pts[2];
1871 numIterPts += 2;
1872 break;
1873 case SkPath::kCubic_Verb:
1874 REPORTER_ASSERT(reporter, numIterPts < numPoints + 3);
1875 REPORTER_ASSERT(reporter, pts[0] == lastPt);
1876 REPORTER_ASSERT(reporter, pts[1] == expectedPts[numIterPts]);
1877 REPORTER_ASSERT(reporter, pts[2] == expectedPts[numIterPts + 1]);
1878 REPORTER_ASSERT(reporter, pts[3] == expectedPts[numIterPts + 2]);
1879 lastPt = pts[3];
1880 numIterPts += 3;
1881 break;
1882 case SkPath::kClose_Verb:
1883 REPORTER_ASSERT(reporter, pts[0] == lastMoveTo);
1884 lastPt = lastMoveTo;
1885 break;
1886 default:;
1887 }
1888 }
1889 REPORTER_ASSERT(reporter, numIterPts == numPoints);
1890 REPORTER_ASSERT(reporter, numIterVerbs == numVerbs);
1891 }
1892}
1893
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001894static void check_for_circle(skiatest::Reporter* reporter,
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001895 const SkPath& path,
1896 bool expectedCircle,
1897 SkPath::Direction expectedDir) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001898 SkRect rect;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001899 REPORTER_ASSERT(reporter, path.isOval(&rect) == expectedCircle);
1900 REPORTER_ASSERT(reporter, path.cheapIsDirection(expectedDir));
skia.committer@gmail.comfbb0ed92012-11-13 21:46:06 +00001901
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001902 if (expectedCircle) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001903 REPORTER_ASSERT(reporter, rect.height() == rect.width());
1904 }
1905}
1906
1907static void test_circle_skew(skiatest::Reporter* reporter,
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001908 const SkPath& path,
1909 SkPath::Direction dir) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001910 SkPath tmp;
1911
1912 SkMatrix m;
1913 m.setSkew(SkIntToScalar(3), SkIntToScalar(5));
1914 path.transform(m, &tmp);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001915 // this matrix reverses the direction.
1916 if (SkPath::kCCW_Direction == dir) {
1917 dir = SkPath::kCW_Direction;
1918 } else {
1919 SkASSERT(SkPath::kCW_Direction == dir);
1920 dir = SkPath::kCCW_Direction;
1921 }
1922 check_for_circle(reporter, tmp, false, dir);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001923}
1924
1925static void test_circle_translate(skiatest::Reporter* reporter,
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001926 const SkPath& path,
1927 SkPath::Direction dir) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001928 SkPath tmp;
1929
1930 // translate at small offset
1931 SkMatrix m;
1932 m.setTranslate(SkIntToScalar(15), SkIntToScalar(15));
1933 path.transform(m, &tmp);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001934 check_for_circle(reporter, tmp, true, dir);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001935
1936 tmp.reset();
1937 m.reset();
1938
1939 // translate at a relatively big offset
1940 m.setTranslate(SkIntToScalar(1000), SkIntToScalar(1000));
1941 path.transform(m, &tmp);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001942 check_for_circle(reporter, tmp, true, dir);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001943}
1944
1945static void test_circle_rotate(skiatest::Reporter* reporter,
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001946 const SkPath& path,
1947 SkPath::Direction dir) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001948 for (int angle = 0; angle < 360; ++angle) {
1949 SkPath tmp;
1950 SkMatrix m;
1951 m.setRotate(SkIntToScalar(angle));
1952 path.transform(m, &tmp);
1953
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001954 // TODO: a rotated circle whose rotated angle is not a multiple of 90
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001955 // degrees is not an oval anymore, this can be improved. we made this
1956 // for the simplicity of our implementation.
1957 if (angle % 90 == 0) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001958 check_for_circle(reporter, tmp, true, dir);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001959 } else {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001960 check_for_circle(reporter, tmp, false, dir);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001961 }
1962 }
1963}
1964
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001965static void test_circle_mirror_x(skiatest::Reporter* reporter,
1966 const SkPath& path,
1967 SkPath::Direction dir) {
1968 SkPath tmp;
1969 SkMatrix m;
1970 m.reset();
1971 m.setScaleX(-SK_Scalar1);
1972 path.transform(m, &tmp);
1973
1974 if (SkPath::kCW_Direction == dir) {
1975 dir = SkPath::kCCW_Direction;
1976 } else {
1977 SkASSERT(SkPath::kCCW_Direction == dir);
1978 dir = SkPath::kCW_Direction;
1979 }
1980
1981 check_for_circle(reporter, tmp, true, dir);
1982}
1983
1984static void test_circle_mirror_y(skiatest::Reporter* reporter,
1985 const SkPath& path,
1986 SkPath::Direction dir) {
1987 SkPath tmp;
1988 SkMatrix m;
1989 m.reset();
1990 m.setScaleY(-SK_Scalar1);
1991 path.transform(m, &tmp);
1992
1993 if (SkPath::kCW_Direction == dir) {
1994 dir = SkPath::kCCW_Direction;
1995 } else {
1996 SkASSERT(SkPath::kCCW_Direction == dir);
1997 dir = SkPath::kCW_Direction;
1998 }
1999
2000 check_for_circle(reporter, tmp, true, dir);
2001}
2002
2003static void test_circle_mirror_xy(skiatest::Reporter* reporter,
2004 const SkPath& path,
2005 SkPath::Direction dir) {
2006 SkPath tmp;
2007 SkMatrix m;
2008 m.reset();
2009 m.setScaleX(-SK_Scalar1);
2010 m.setScaleY(-SK_Scalar1);
2011 path.transform(m, &tmp);
2012
2013 check_for_circle(reporter, tmp, true, dir);
2014}
2015
bsalomon@google.com6aa29652012-04-18 13:29:52 +00002016static void test_circle_with_direction(skiatest::Reporter* reporter,
2017 SkPath::Direction dir) {
2018 SkPath path;
2019
2020 // circle at origin
2021 path.addCircle(0, 0, SkIntToScalar(20), dir);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002022 check_for_circle(reporter, path, true, dir);
2023 test_circle_rotate(reporter, path, dir);
2024 test_circle_translate(reporter, path, dir);
2025 test_circle_skew(reporter, path, dir);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00002026
2027 // circle at an offset at (10, 10)
2028 path.reset();
2029 path.addCircle(SkIntToScalar(10), SkIntToScalar(10),
2030 SkIntToScalar(20), dir);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002031 check_for_circle(reporter, path, true, dir);
2032 test_circle_rotate(reporter, path, dir);
2033 test_circle_translate(reporter, path, dir);
2034 test_circle_skew(reporter, path, dir);
2035 test_circle_mirror_x(reporter, path, dir);
2036 test_circle_mirror_y(reporter, path, dir);
2037 test_circle_mirror_xy(reporter, path, dir);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00002038}
2039
2040static void test_circle_with_add_paths(skiatest::Reporter* reporter) {
2041 SkPath path;
2042 SkPath circle;
2043 SkPath rect;
2044 SkPath empty;
2045
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002046 static const SkPath::Direction kCircleDir = SkPath::kCW_Direction;
2047 static const SkPath::Direction kCircleDirOpposite = SkPath::kCCW_Direction;
2048
2049 circle.addCircle(0, 0, SkIntToScalar(10), kCircleDir);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00002050 rect.addRect(SkIntToScalar(5), SkIntToScalar(5),
2051 SkIntToScalar(20), SkIntToScalar(20), SkPath::kCW_Direction);
2052
2053 SkMatrix translate;
2054 translate.setTranslate(SkIntToScalar(12), SkIntToScalar(12));
2055
2056 // For simplicity, all the path concatenation related operations
2057 // would mark it non-circle, though in theory it's still a circle.
2058
2059 // empty + circle (translate)
2060 path = empty;
2061 path.addPath(circle, translate);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002062 check_for_circle(reporter, path, false, kCircleDir);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00002063
2064 // circle + empty (translate)
2065 path = circle;
2066 path.addPath(empty, translate);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002067 check_for_circle(reporter, path, false, kCircleDir);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00002068
2069 // test reverseAddPath
2070 path = circle;
2071 path.reverseAddPath(rect);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002072 check_for_circle(reporter, path, false, kCircleDirOpposite);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00002073}
2074
2075static void test_circle(skiatest::Reporter* reporter) {
2076 test_circle_with_direction(reporter, SkPath::kCW_Direction);
2077 test_circle_with_direction(reporter, SkPath::kCCW_Direction);
2078
2079 // multiple addCircle()
2080 SkPath path;
2081 path.addCircle(0, 0, SkIntToScalar(10), SkPath::kCW_Direction);
2082 path.addCircle(0, 0, SkIntToScalar(20), SkPath::kCW_Direction);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002083 check_for_circle(reporter, path, false, SkPath::kCW_Direction);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00002084
2085 // some extra lineTo() would make isOval() fail
2086 path.reset();
2087 path.addCircle(0, 0, SkIntToScalar(10), SkPath::kCW_Direction);
2088 path.lineTo(0, 0);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002089 check_for_circle(reporter, path, false, SkPath::kCW_Direction);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00002090
2091 // not back to the original point
2092 path.reset();
2093 path.addCircle(0, 0, SkIntToScalar(10), SkPath::kCW_Direction);
2094 path.setLastPt(SkIntToScalar(5), SkIntToScalar(5));
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002095 check_for_circle(reporter, path, false, SkPath::kCW_Direction);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00002096
2097 test_circle_with_add_paths(reporter);
2098}
2099
2100static void test_oval(skiatest::Reporter* reporter) {
2101 SkRect rect;
2102 SkMatrix m;
2103 SkPath path;
2104
2105 rect = SkRect::MakeWH(SkIntToScalar(30), SkIntToScalar(50));
2106 path.addOval(rect);
2107
2108 REPORTER_ASSERT(reporter, path.isOval(NULL));
2109
2110 m.setRotate(SkIntToScalar(90));
2111 SkPath tmp;
2112 path.transform(m, &tmp);
2113 // an oval rotated 90 degrees is still an oval.
2114 REPORTER_ASSERT(reporter, tmp.isOval(NULL));
2115
2116 m.reset();
2117 m.setRotate(SkIntToScalar(30));
2118 tmp.reset();
2119 path.transform(m, &tmp);
2120 // an oval rotated 30 degrees is not an oval anymore.
2121 REPORTER_ASSERT(reporter, !tmp.isOval(NULL));
2122
2123 // since empty path being transformed.
2124 path.reset();
2125 tmp.reset();
2126 m.reset();
2127 path.transform(m, &tmp);
2128 REPORTER_ASSERT(reporter, !tmp.isOval(NULL));
2129
2130 // empty path is not an oval
2131 tmp.reset();
2132 REPORTER_ASSERT(reporter, !tmp.isOval(NULL));
2133
2134 // only has moveTo()s
2135 tmp.reset();
2136 tmp.moveTo(0, 0);
2137 tmp.moveTo(SkIntToScalar(10), SkIntToScalar(10));
2138 REPORTER_ASSERT(reporter, !tmp.isOval(NULL));
2139
2140 // mimic WebKit's calling convention,
2141 // call moveTo() first and then call addOval()
2142 path.reset();
2143 path.moveTo(0, 0);
2144 path.addOval(rect);
2145 REPORTER_ASSERT(reporter, path.isOval(NULL));
2146
2147 // copy path
2148 path.reset();
2149 tmp.reset();
2150 tmp.addOval(rect);
2151 path = tmp;
2152 REPORTER_ASSERT(reporter, path.isOval(NULL));
2153}
2154
caryclark@google.com42639cd2012-06-06 12:03:39 +00002155static void TestPath(skiatest::Reporter* reporter) {
reed@android.com60bc6d52010-02-11 11:09:39 +00002156 SkTSize<SkScalar>::Make(3,4);
2157
reed@android.com3abec1d2009-03-02 05:36:20 +00002158 SkPath p, p2;
2159 SkRect bounds, bounds2;
reed@android.com80e39a72009-04-02 16:59:40 +00002160
reed@android.com3abec1d2009-03-02 05:36:20 +00002161 REPORTER_ASSERT(reporter, p.isEmpty());
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002162 REPORTER_ASSERT(reporter, 0 == p.countPoints());
bsalomon@google.comdf9d6562012-06-07 21:43:15 +00002163 REPORTER_ASSERT(reporter, 0 == p.countVerbs());
reed@google.com10296cc2011-09-21 12:29:05 +00002164 REPORTER_ASSERT(reporter, 0 == p.getSegmentMasks());
reed@google.comb54455e2011-05-16 14:16:04 +00002165 REPORTER_ASSERT(reporter, p.isConvex());
reed@android.com3abec1d2009-03-02 05:36:20 +00002166 REPORTER_ASSERT(reporter, p.getFillType() == SkPath::kWinding_FillType);
2167 REPORTER_ASSERT(reporter, !p.isInverseFillType());
2168 REPORTER_ASSERT(reporter, p == p2);
2169 REPORTER_ASSERT(reporter, !(p != p2));
2170
reed@android.comd252db02009-04-01 18:31:44 +00002171 REPORTER_ASSERT(reporter, p.getBounds().isEmpty());
reed@android.com80e39a72009-04-02 16:59:40 +00002172
reed@android.com3abec1d2009-03-02 05:36:20 +00002173 bounds.set(0, 0, SK_Scalar1, SK_Scalar1);
reed@android.com6b82d1a2009-06-03 02:35:01 +00002174
reed@android.com6b82d1a2009-06-03 02:35:01 +00002175 p.addRoundRect(bounds, SK_Scalar1, SK_Scalar1);
2176 check_convex_bounds(reporter, p, bounds);
reed@google.com10296cc2011-09-21 12:29:05 +00002177 // we have quads or cubics
2178 REPORTER_ASSERT(reporter, p.getSegmentMasks() & kCurveSegmentMask);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002179 REPORTER_ASSERT(reporter, !p.isEmpty());
reed@google.com62047cf2011-02-07 19:39:09 +00002180
reed@android.com6b82d1a2009-06-03 02:35:01 +00002181 p.reset();
reed@google.com10296cc2011-09-21 12:29:05 +00002182 REPORTER_ASSERT(reporter, 0 == p.getSegmentMasks());
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002183 REPORTER_ASSERT(reporter, p.isEmpty());
reed@google.com10296cc2011-09-21 12:29:05 +00002184
reed@android.com6b82d1a2009-06-03 02:35:01 +00002185 p.addOval(bounds);
2186 check_convex_bounds(reporter, p, bounds);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002187 REPORTER_ASSERT(reporter, !p.isEmpty());
reed@google.com62047cf2011-02-07 19:39:09 +00002188
reed@android.com6b82d1a2009-06-03 02:35:01 +00002189 p.reset();
reed@android.com3abec1d2009-03-02 05:36:20 +00002190 p.addRect(bounds);
reed@android.com6b82d1a2009-06-03 02:35:01 +00002191 check_convex_bounds(reporter, p, bounds);
reed@google.com10296cc2011-09-21 12:29:05 +00002192 // we have only lines
2193 REPORTER_ASSERT(reporter, SkPath::kLine_SegmentMask == p.getSegmentMasks());
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002194 REPORTER_ASSERT(reporter, !p.isEmpty());
reed@android.com3abec1d2009-03-02 05:36:20 +00002195
2196 REPORTER_ASSERT(reporter, p != p2);
2197 REPORTER_ASSERT(reporter, !(p == p2));
2198
bsalomon@google.comdf9d6562012-06-07 21:43:15 +00002199 // do getPoints and getVerbs return the right result
2200 REPORTER_ASSERT(reporter, p.getPoints(NULL, 0) == 4);
2201 REPORTER_ASSERT(reporter, p.getVerbs(NULL, 0) == 5);
reed@android.com3abec1d2009-03-02 05:36:20 +00002202 SkPoint pts[4];
2203 int count = p.getPoints(pts, 4);
2204 REPORTER_ASSERT(reporter, count == 4);
bsalomon@google.comdf9d6562012-06-07 21:43:15 +00002205 uint8_t verbs[6];
2206 verbs[5] = 0xff;
2207 p.getVerbs(verbs, 5);
2208 REPORTER_ASSERT(reporter, SkPath::kMove_Verb == verbs[0]);
2209 REPORTER_ASSERT(reporter, SkPath::kLine_Verb == verbs[1]);
2210 REPORTER_ASSERT(reporter, SkPath::kLine_Verb == verbs[2]);
2211 REPORTER_ASSERT(reporter, SkPath::kLine_Verb == verbs[3]);
2212 REPORTER_ASSERT(reporter, SkPath::kClose_Verb == verbs[4]);
2213 REPORTER_ASSERT(reporter, 0xff == verbs[5]);
reed@android.com3abec1d2009-03-02 05:36:20 +00002214 bounds2.set(pts, 4);
2215 REPORTER_ASSERT(reporter, bounds == bounds2);
reed@android.com80e39a72009-04-02 16:59:40 +00002216
reed@android.com3abec1d2009-03-02 05:36:20 +00002217 bounds.offset(SK_Scalar1*3, SK_Scalar1*4);
2218 p.offset(SK_Scalar1*3, SK_Scalar1*4);
reed@android.comd252db02009-04-01 18:31:44 +00002219 REPORTER_ASSERT(reporter, bounds == p.getBounds());
reed@android.com3abec1d2009-03-02 05:36:20 +00002220
reed@android.com3abec1d2009-03-02 05:36:20 +00002221 REPORTER_ASSERT(reporter, p.isRect(NULL));
caryclark@google.comf1316942011-07-26 19:54:45 +00002222 bounds2.setEmpty();
reed@android.com3abec1d2009-03-02 05:36:20 +00002223 REPORTER_ASSERT(reporter, p.isRect(&bounds2));
2224 REPORTER_ASSERT(reporter, bounds == bounds2);
reed@android.com80e39a72009-04-02 16:59:40 +00002225
reed@android.com3abec1d2009-03-02 05:36:20 +00002226 // now force p to not be a rect
2227 bounds.set(0, 0, SK_Scalar1/2, SK_Scalar1/2);
2228 p.addRect(bounds);
2229 REPORTER_ASSERT(reporter, !p.isRect(NULL));
reed@android.com3abec1d2009-03-02 05:36:20 +00002230
reed@google.com7e6c4d12012-05-10 14:05:43 +00002231 test_isLine(reporter);
2232 test_isRect(reporter);
caryclark@google.com56f233a2012-11-19 13:06:06 +00002233 test_isNestedRects(reporter);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002234 test_zero_length_paths(reporter);
reed@google.comcabaf1d2012-01-11 21:03:05 +00002235 test_direction(reporter);
reed@google.com04863fa2011-05-15 04:08:24 +00002236 test_convexity(reporter);
reed@google.com7c424812011-05-15 04:38:34 +00002237 test_convexity2(reporter);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +00002238 test_conservativelyContains(reporter);
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00002239 test_close(reporter);
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002240 test_segment_masks(reporter);
reed@google.com53effc52011-09-21 19:05:12 +00002241 test_flattening(reporter);
2242 test_transform(reporter);
reed@google.com3563c9e2011-11-14 19:34:57 +00002243 test_bounds(reporter);
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002244 test_iter(reporter);
2245 test_raw_iter(reporter);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00002246 test_circle(reporter);
2247 test_oval(reporter);
reed@google.com8b06f1a2012-05-29 12:03:46 +00002248 test_strokerec(reporter);
reed@google.com744faba2012-05-29 19:54:52 +00002249 test_addPoly(reporter);
reed@google.com0bb18bb2012-07-26 15:20:36 +00002250 test_isfinite(reporter);
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002251 test_isfinite_after_transform(reporter);
reed@google.com8cae8352012-09-14 15:18:41 +00002252 test_tricky_cubic(reporter);
robertphillips@google.comb95eaa82012-10-18 15:26:12 +00002253 test_arb_round_rect_is_convex(reporter);
robertphillips@google.com158618e2012-10-23 16:56:56 +00002254 test_arb_zero_rad_round_rect_is_rect(reporter);
reed@google.coma8790de2012-10-24 21:04:04 +00002255 test_addrect_isfinite(reporter);
reed@google.com848148e2013-01-15 15:51:59 +00002256 test_clipped_cubic(reporter);
reed@android.com3abec1d2009-03-02 05:36:20 +00002257}
2258
2259#include "TestClassDef.h"
2260DEFINE_TESTCLASS("Path", PathTestClass, TestPath)