1 /*
2 * Copyright 2014 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8 #include "include/core/SkPaint.h"
9 #include "include/core/SkPath.h"
10 #include "include/core/SkTime.h"
11 #include "include/utils/SkRandom.h"
12 #include "src/core/SkPointPriv.h"
13 #include "src/core/SkStrokerPriv.h"
14 #include "src/pathops/SkPathOpsCubic.h"
15 #include "tests/PathOpsCubicIntersectionTestData.h"
16 #include "tests/PathOpsQuadIntersectionTestData.h"
17 #include "tests/Test.h"
18 #include "tools/flags/CommandLineFlags.h"
19
20 static DEFINE_bool(timeout, true, "run until alloted time expires");
21
22 #define MS_TEST_DURATION 10
23
24 const SkScalar widths[] = {-FLT_MAX, -1, -0.1f, -FLT_EPSILON, 0, FLT_EPSILON,
25 0.0000001f, 0.000001f, 0.00001f, 0.0001f, 0.001f, 0.01f,
26 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 1, 1.1f, 2, 10, 10e2f, 10e3f, 10e4f, 10e5f, 10e6f, 10e7f,
27 10e8f, 10e9f, 10e10f, 10e20f, FLT_MAX };
28 size_t widths_count = SK_ARRAY_COUNT(widths);
29
pathTest(const SkPath & path)30 static void pathTest(const SkPath& path) {
31 SkPaint p;
32 SkPath fill;
33 p.setStyle(SkPaint::kStroke_Style);
34 for (size_t index = 0; index < widths_count; ++index) {
35 p.setStrokeWidth(widths[index]);
36 p.getFillPath(path, &fill);
37 }
38 }
39
cubicTest(const SkPoint c[4])40 static void cubicTest(const SkPoint c[4]) {
41 SkPath path;
42 path.moveTo(c[0].fX, c[0].fY);
43 path.cubicTo(c[1].fX, c[1].fY, c[2].fX, c[2].fY, c[3].fX, c[3].fY);
44 pathTest(path);
45 }
46
quadTest(const SkPoint c[3])47 static void quadTest(const SkPoint c[3]) {
48 SkPath path;
49 path.moveTo(c[0].fX, c[0].fY);
50 path.quadTo(c[1].fX, c[1].fY, c[2].fX, c[2].fY);
51 pathTest(path);
52 }
53
cubicSetTest(const CubicPts * dCubic,size_t count)54 static void cubicSetTest(const CubicPts* dCubic, size_t count) {
55 skiatest::Timer timer;
56 for (size_t index = 0; index < count; ++index) {
57 const CubicPts& dPts = dCubic[index];
58 SkDCubic d;
59 d.debugSet(dPts.fPts);
60 SkPoint c[4] = { {(float) d[0].fX, (float) d[0].fY}, {(float) d[1].fX, (float) d[1].fY},
61 {(float) d[2].fX, (float) d[2].fY}, {(float) d[3].fX, (float) d[3].fY} };
62 cubicTest(c);
63 if (FLAGS_timeout && timer.elapsedMs() > MS_TEST_DURATION) {
64 return;
65 }
66 }
67 }
68
cubicPairSetTest(const CubicPts dCubic[][2],size_t count)69 static void cubicPairSetTest(const CubicPts dCubic[][2], size_t count) {
70 skiatest::Timer timer;
71 for (size_t index = 0; index < count; ++index) {
72 for (int pair = 0; pair < 2; ++pair) {
73 const CubicPts& dPts = dCubic[index][pair];
74 SkDCubic d;
75 d.debugSet(dPts.fPts);
76 SkPoint c[4] = { {(float) d[0].fX, (float) d[0].fY}, {(float) d[1].fX, (float) d[1].fY},
77 {(float) d[2].fX, (float) d[2].fY}, {(float) d[3].fX, (float) d[3].fY} };
78 cubicTest(c);
79 if (FLAGS_timeout && timer.elapsedMs() > MS_TEST_DURATION) {
80 return;
81 }
82 }
83 }
84 }
85
quadSetTest(const QuadPts * dQuad,size_t count)86 static void quadSetTest(const QuadPts* dQuad, size_t count) {
87 skiatest::Timer timer;
88 for (size_t index = 0; index < count; ++index) {
89 const QuadPts& dPts = dQuad[index];
90 SkDQuad d;
91 d.debugSet(dPts.fPts);
92 SkPoint c[3] = { {(float) d[0].fX, (float) d[0].fY}, {(float) d[1].fX, (float) d[1].fY},
93 {(float) d[2].fX, (float) d[2].fY} };
94 quadTest(c);
95 if (FLAGS_timeout && timer.elapsedMs() > MS_TEST_DURATION) {
96 return;
97 }
98 }
99 }
100
quadPairSetTest(const QuadPts dQuad[][2],size_t count)101 static void quadPairSetTest(const QuadPts dQuad[][2], size_t count) {
102 skiatest::Timer timer;
103 for (size_t index = 0; index < count; ++index) {
104 for (int pair = 0; pair < 2; ++pair) {
105 const QuadPts& dPts = dQuad[index][pair];
106 SkDQuad d;
107 d.debugSet(dPts.fPts);
108 SkPoint c[3] = { {(float) d[0].fX, (float) d[0].fY}, {(float) d[1].fX, (float) d[1].fY},
109 {(float) d[2].fX, (float) d[2].fY} };
110 quadTest(c);
111 if (FLAGS_timeout && timer.elapsedMs() > MS_TEST_DURATION) {
112 return;
113 }
114 }
115 }
116 }
117
DEF_TEST(QuadStrokerSet,reporter)118 DEF_TEST(QuadStrokerSet, reporter) {
119 quadSetTest(quadraticLines, quadraticLines_count);
120 quadSetTest(quadraticPoints, quadraticPoints_count);
121 quadSetTest(quadraticModEpsilonLines, quadraticModEpsilonLines_count);
122 quadPairSetTest(quadraticTests, quadraticTests_count);
123 }
124
DEF_TEST(CubicStrokerSet,reporter)125 DEF_TEST(CubicStrokerSet, reporter) {
126 cubicSetTest(pointDegenerates, pointDegenerates_count);
127 cubicSetTest(notPointDegenerates, notPointDegenerates_count);
128 cubicSetTest(lines, lines_count);
129 cubicSetTest(notLines, notLines_count);
130 cubicSetTest(modEpsilonLines, modEpsilonLines_count);
131 cubicSetTest(lessEpsilonLines, lessEpsilonLines_count);
132 cubicSetTest(negEpsilonLines, negEpsilonLines_count);
133 cubicPairSetTest(tests, tests_count);
134 }
135
unbounded(SkRandom & r)136 static SkScalar unbounded(SkRandom& r) {
137 uint32_t val = r.nextU();
138 return SkBits2Float(val);
139 }
140
unboundedPos(SkRandom & r)141 static SkScalar unboundedPos(SkRandom& r) {
142 uint32_t val = r.nextU() & 0x7fffffff;
143 return SkBits2Float(val);
144 }
145
DEF_TEST(QuadStrokerUnbounded,reporter)146 DEF_TEST(QuadStrokerUnbounded, reporter) {
147 SkRandom r;
148 SkPaint p;
149 p.setStyle(SkPaint::kStroke_Style);
150 #if defined(SK_DEBUG) && QUAD_STROKE_APPROX_EXTENDED_DEBUGGING
151 int best = 0;
152 sk_bzero(gMaxRecursion, sizeof(gMaxRecursion[0]) * 3);
153 #endif
154 skiatest::Timer timer;
155 for (int i = 0; i < 1000000; ++i) {
156 SkPath path, fill;
157 path.moveTo(unbounded(r), unbounded(r));
158 path.quadTo(unbounded(r), unbounded(r), unbounded(r), unbounded(r));
159 p.setStrokeWidth(unboundedPos(r));
160 p.getFillPath(path, &fill);
161 #if defined(SK_DEBUG) && QUAD_STROKE_APPROX_EXTENDED_DEBUGGING
162 if (best < gMaxRecursion[2]) {
163 if (reporter->verbose()) {
164 SkDebugf("\n%s quad=%d width=%1.9g\n", __FUNCTION__, gMaxRecursion[2],
165 p.getStrokeWidth());
166 path.dumpHex();
167 SkDebugf("fill:\n");
168 fill.dumpHex();
169 }
170 best = gMaxRecursion[2];
171 }
172 #endif
173 if (FLAGS_timeout && timer.elapsedMs() > MS_TEST_DURATION) {
174 return;
175 }
176 }
177 #if defined(SK_DEBUG) && QUAD_STROKE_APPROX_EXTENDED_DEBUGGING
178 if (reporter->verbose()) {
179 SkDebugf("\n%s max quad=%d\n", __FUNCTION__, best);
180 }
181 #endif
182 }
183
DEF_TEST(CubicStrokerUnbounded,reporter)184 DEF_TEST(CubicStrokerUnbounded, reporter) {
185 SkRandom r;
186 SkPaint p;
187 p.setStyle(SkPaint::kStroke_Style);
188 #if defined(SK_DEBUG) && QUAD_STROKE_APPROX_EXTENDED_DEBUGGING
189 int bestTan = 0;
190 int bestCubic = 0;
191 sk_bzero(gMaxRecursion, sizeof(gMaxRecursion[0]) * 3);
192 #endif
193 skiatest::Timer timer;
194 for (int i = 0; i < 1000000; ++i) {
195 SkPath path, fill;
196 path.moveTo(unbounded(r), unbounded(r));
197 path.cubicTo(unbounded(r), unbounded(r), unbounded(r), unbounded(r),
198 unbounded(r), unbounded(r));
199 p.setStrokeWidth(unboundedPos(r));
200 p.getFillPath(path, &fill);
201 #if defined(SK_DEBUG) && QUAD_STROKE_APPROX_EXTENDED_DEBUGGING
202 if (bestTan < gMaxRecursion[0] || bestCubic < gMaxRecursion[1]) {
203 if (reporter->verbose()) {
204 SkDebugf("\n%s tan=%d cubic=%d width=%1.9g\n", __FUNCTION__, gMaxRecursion[0],
205 gMaxRecursion[1], p.getStrokeWidth());
206 path.dumpHex();
207 SkDebugf("fill:\n");
208 fill.dumpHex();
209 }
210 bestTan = std::max(bestTan, gMaxRecursion[0]);
211 bestCubic = std::max(bestCubic, gMaxRecursion[1]);
212 }
213 #endif
214 if (FLAGS_timeout && timer.elapsedMs() > MS_TEST_DURATION) {
215 return;
216 }
217 }
218 #if defined(SK_DEBUG) && QUAD_STROKE_APPROX_EXTENDED_DEBUGGING
219 if (reporter->verbose()) {
220 SkDebugf("\n%s max tan=%d cubic=%d\n", __FUNCTION__, bestTan, bestCubic);
221 }
222 #endif
223 }
224
DEF_TEST(QuadStrokerConstrained,reporter)225 DEF_TEST(QuadStrokerConstrained, reporter) {
226 SkRandom r;
227 SkPaint p;
228 p.setStyle(SkPaint::kStroke_Style);
229 #if defined(SK_DEBUG) && QUAD_STROKE_APPROX_EXTENDED_DEBUGGING
230 int best = 0;
231 sk_bzero(gMaxRecursion, sizeof(gMaxRecursion[0]) * 3);
232 #endif
233 skiatest::Timer timer;
234 for (int i = 0; i < 1000000; ++i) {
235 SkPath path, fill;
236 SkPoint quad[3];
237 quad[0].fX = r.nextRangeF(0, 500);
238 quad[0].fY = r.nextRangeF(0, 500);
239 const SkScalar halfSquared = 0.5f * 0.5f;
240 do {
241 quad[1].fX = r.nextRangeF(0, 500);
242 quad[1].fY = r.nextRangeF(0, 500);
243 } while (SkPointPriv::DistanceToSqd(quad[0], quad[1]) < halfSquared);
244 do {
245 quad[2].fX = r.nextRangeF(0, 500);
246 quad[2].fY = r.nextRangeF(0, 500);
247 } while (SkPointPriv::DistanceToSqd(quad[0], quad[2]) < halfSquared
248 || SkPointPriv::DistanceToSqd(quad[1], quad[2]) < halfSquared);
249 path.moveTo(quad[0].fX, quad[0].fY);
250 path.quadTo(quad[1].fX, quad[1].fY, quad[2].fX, quad[2].fY);
251 p.setStrokeWidth(r.nextRangeF(0, 500));
252 p.getFillPath(path, &fill);
253 #if defined(SK_DEBUG) && QUAD_STROKE_APPROX_EXTENDED_DEBUGGING
254 if (best < gMaxRecursion[2]) {
255 if (reporter->verbose()) {
256 SkDebugf("\n%s quad=%d width=%1.9g\n", __FUNCTION__, gMaxRecursion[2],
257 p.getStrokeWidth());
258 path.dumpHex();
259 SkDebugf("fill:\n");
260 fill.dumpHex();
261 }
262 best = gMaxRecursion[2];
263 }
264 #endif
265 if (FLAGS_timeout && timer.elapsedMs() > MS_TEST_DURATION) {
266 return;
267 }
268 }
269 #if defined(SK_DEBUG) && QUAD_STROKE_APPROX_EXTENDED_DEBUGGING
270 if (reporter->verbose()) {
271 SkDebugf("\n%s max quad=%d\n", __FUNCTION__, best);
272 }
273 #endif
274 }
275
DEF_TEST(CubicStrokerConstrained,reporter)276 DEF_TEST(CubicStrokerConstrained, reporter) {
277 SkRandom r;
278 SkPaint p;
279 p.setStyle(SkPaint::kStroke_Style);
280 #if defined(SK_DEBUG) && QUAD_STROKE_APPROX_EXTENDED_DEBUGGING
281 int bestTan = 0;
282 int bestCubic = 0;
283 sk_bzero(gMaxRecursion, sizeof(gMaxRecursion[0]) * 3);
284 #endif
285 skiatest::Timer timer;
286 for (int i = 0; i < 1000000; ++i) {
287 SkPath path, fill;
288 SkPoint cubic[4];
289 cubic[0].fX = r.nextRangeF(0, 500);
290 cubic[0].fY = r.nextRangeF(0, 500);
291 const SkScalar halfSquared = 0.5f * 0.5f;
292 do {
293 cubic[1].fX = r.nextRangeF(0, 500);
294 cubic[1].fY = r.nextRangeF(0, 500);
295 } while (SkPointPriv::DistanceToSqd(cubic[0], cubic[1]) < halfSquared);
296 do {
297 cubic[2].fX = r.nextRangeF(0, 500);
298 cubic[2].fY = r.nextRangeF(0, 500);
299 } while ( SkPointPriv::DistanceToSqd(cubic[0], cubic[2]) < halfSquared
300 || SkPointPriv::DistanceToSqd(cubic[1], cubic[2]) < halfSquared);
301 do {
302 cubic[3].fX = r.nextRangeF(0, 500);
303 cubic[3].fY = r.nextRangeF(0, 500);
304 } while ( SkPointPriv::DistanceToSqd(cubic[0], cubic[3]) < halfSquared
305 || SkPointPriv::DistanceToSqd(cubic[1], cubic[3]) < halfSquared
306 || SkPointPriv::DistanceToSqd(cubic[2], cubic[3]) < halfSquared);
307 path.moveTo(cubic[0].fX, cubic[0].fY);
308 path.cubicTo(cubic[1].fX, cubic[1].fY, cubic[2].fX, cubic[2].fY, cubic[3].fX, cubic[3].fY);
309 p.setStrokeWidth(r.nextRangeF(0, 500));
310 p.getFillPath(path, &fill);
311 #if defined(SK_DEBUG) && QUAD_STROKE_APPROX_EXTENDED_DEBUGGING
312 if (bestTan < gMaxRecursion[0] || bestCubic < gMaxRecursion[1]) {
313 if (reporter->verbose()) {
314 SkDebugf("\n%s tan=%d cubic=%d width=%1.9g\n", __FUNCTION__, gMaxRecursion[0],
315 gMaxRecursion[1], p.getStrokeWidth());
316 path.dumpHex();
317 SkDebugf("fill:\n");
318 fill.dumpHex();
319 }
320 bestTan = std::max(bestTan, gMaxRecursion[0]);
321 bestCubic = std::max(bestCubic, gMaxRecursion[1]);
322 }
323 #endif
324 if (FLAGS_timeout && timer.elapsedMs() > MS_TEST_DURATION) {
325 return;
326 }
327 }
328 #if defined(SK_DEBUG) && QUAD_STROKE_APPROX_EXTENDED_DEBUGGING
329 if (reporter->verbose()) {
330 SkDebugf("\n%s max tan=%d cubic=%d\n", __FUNCTION__, bestTan, bestCubic);
331 }
332 #endif
333 }
334
DEF_TEST(QuadStrokerRange,reporter)335 DEF_TEST(QuadStrokerRange, reporter) {
336 SkRandom r;
337 SkPaint p;
338 p.setStyle(SkPaint::kStroke_Style);
339 #if defined(SK_DEBUG) && QUAD_STROKE_APPROX_EXTENDED_DEBUGGING
340 int best = 0;
341 sk_bzero(gMaxRecursion, sizeof(gMaxRecursion[0]) * 3);
342 #endif
343 skiatest::Timer timer;
344 for (int i = 0; i < 1000000; ++i) {
345 SkPath path, fill;
346 SkPoint quad[3];
347 quad[0].fX = r.nextRangeF(0, 500);
348 quad[0].fY = r.nextRangeF(0, 500);
349 quad[1].fX = r.nextRangeF(0, 500);
350 quad[1].fY = r.nextRangeF(0, 500);
351 quad[2].fX = r.nextRangeF(0, 500);
352 quad[2].fY = r.nextRangeF(0, 500);
353 path.moveTo(quad[0].fX, quad[0].fY);
354 path.quadTo(quad[1].fX, quad[1].fY, quad[2].fX, quad[2].fY);
355 p.setStrokeWidth(r.nextRangeF(0, 500));
356 p.getFillPath(path, &fill);
357 #if defined(SK_DEBUG) && QUAD_STROKE_APPROX_EXTENDED_DEBUGGING
358 if (best < gMaxRecursion[2]) {
359 if (reporter->verbose()) {
360 SkDebugf("\n%s quad=%d width=%1.9g\n", __FUNCTION__, gMaxRecursion[2],
361 p.getStrokeWidth());
362 path.dumpHex();
363 SkDebugf("fill:\n");
364 fill.dumpHex();
365 }
366 best = gMaxRecursion[2];
367 }
368 #endif
369 if (FLAGS_timeout && timer.elapsedMs() > MS_TEST_DURATION) {
370 return;
371 }
372 }
373 #if defined(SK_DEBUG) && QUAD_STROKE_APPROX_EXTENDED_DEBUGGING
374 if (reporter->verbose()) {
375 SkDebugf("\n%s max quad=%d\n", __FUNCTION__, best);
376 }
377 #endif
378 }
379
DEF_TEST(CubicStrokerRange,reporter)380 DEF_TEST(CubicStrokerRange, reporter) {
381 SkRandom r;
382 SkPaint p;
383 p.setStyle(SkPaint::kStroke_Style);
384 #if defined(SK_DEBUG) && QUAD_STROKE_APPROX_EXTENDED_DEBUGGING
385 int best[2] = { 0 };
386 sk_bzero(gMaxRecursion, sizeof(gMaxRecursion[0]) * 3);
387 #endif
388 skiatest::Timer timer;
389 for (int i = 0; i < 1000000; ++i) {
390 SkPath path, fill;
391 path.moveTo(r.nextRangeF(0, 500), r.nextRangeF(0, 500));
392 path.cubicTo(r.nextRangeF(0, 500), r.nextRangeF(0, 500), r.nextRangeF(0, 500),
393 r.nextRangeF(0, 500), r.nextRangeF(0, 500), r.nextRangeF(0, 500));
394 p.setStrokeWidth(r.nextRangeF(0, 100));
395 p.getFillPath(path, &fill);
396 #if defined(SK_DEBUG) && QUAD_STROKE_APPROX_EXTENDED_DEBUGGING
397 if (best[0] < gMaxRecursion[0] || best[1] < gMaxRecursion[1]) {
398 if (reporter->verbose()) {
399 SkDebugf("\n%s tan=%d cubic=%d width=%1.9g\n", __FUNCTION__, gMaxRecursion[0],
400 gMaxRecursion[1], p.getStrokeWidth());
401 path.dumpHex();
402 SkDebugf("fill:\n");
403 fill.dumpHex();
404 }
405 best[0] = std::max(best[0], gMaxRecursion[0]);
406 best[1] = std::max(best[1], gMaxRecursion[1]);
407 }
408 #endif
409 if (FLAGS_timeout && timer.elapsedMs() > MS_TEST_DURATION) {
410 return;
411 }
412 }
413 #if defined(SK_DEBUG) && QUAD_STROKE_APPROX_EXTENDED_DEBUGGING
414 if (reporter->verbose()) {
415 SkDebugf("\n%s max tan=%d cubic=%d\n", __FUNCTION__, best[0], best[1]);
416 }
417 #endif
418 }
419
420
DEF_TEST(QuadStrokerOneOff,reporter)421 DEF_TEST(QuadStrokerOneOff, reporter) {
422 #if defined(SK_DEBUG) && QUAD_STROKE_APPROX_EXTENDED_DEBUGGING
423 sk_bzero(gMaxRecursion, sizeof(gMaxRecursion[0]) * 3);
424 #endif
425 SkPaint p;
426 p.setStyle(SkPaint::kStroke_Style);
427 p.setStrokeWidth(SkDoubleToScalar(164.683548));
428
429 SkPath path, fill;
430 path.moveTo(SkBits2Float(0x43c99223), SkBits2Float(0x42b7417e));
431 path.quadTo(SkBits2Float(0x4285d839), SkBits2Float(0x43ed6645), SkBits2Float(0x43c941c8), SkBits2Float(0x42b3ace3));
432 p.getFillPath(path, &fill);
433 if (reporter->verbose()) {
434 SkDebugf("\n%s path\n", __FUNCTION__);
435 path.dump();
436 SkDebugf("fill:\n");
437 fill.dump();
438 }
439 #if defined(SK_DEBUG) && QUAD_STROKE_APPROX_EXTENDED_DEBUGGING
440 if (reporter->verbose()) {
441 SkDebugf("max quad=%d\n", gMaxRecursion[2]);
442 }
443 #endif
444 }
445
DEF_TEST(CubicStrokerOneOff,reporter)446 DEF_TEST(CubicStrokerOneOff, reporter) {
447 #if defined(SK_DEBUG) && QUAD_STROKE_APPROX_EXTENDED_DEBUGGING
448 sk_bzero(gMaxRecursion, sizeof(gMaxRecursion[0]) * 3);
449 #endif
450 SkPaint p;
451 p.setStyle(SkPaint::kStroke_Style);
452 p.setStrokeWidth(SkDoubleToScalar(42.835968));
453
454 SkPath path, fill;
455 path.moveTo(SkBits2Float(0x433f5370), SkBits2Float(0x43d1f4b3));
456 path.cubicTo(SkBits2Float(0x4331cb76), SkBits2Float(0x43ea3340), SkBits2Float(0x4388f498), SkBits2Float(0x42f7f08d), SkBits2Float(0x43f1cd32), SkBits2Float(0x42802ec1));
457 p.getFillPath(path, &fill);
458 if (reporter->verbose()) {
459 SkDebugf("\n%s path\n", __FUNCTION__);
460 path.dump();
461 SkDebugf("fill:\n");
462 fill.dump();
463 }
464 #if defined(SK_DEBUG) && QUAD_STROKE_APPROX_EXTENDED_DEBUGGING
465 if (reporter->verbose()) {
466 SkDebugf("max tan=%d cubic=%d\n", gMaxRecursion[0], gMaxRecursion[1]);
467 }
468 #endif
469 }
470