blob: e46bd18f05b72bf915fbd049958cc728da997979 [file] [log] [blame]
epoger@google.comec3ed6a2011-07-28 14:26:00 +00001/*
2 * Copyright 2011 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
tfarina@chromium.orge4fafb12013-12-12 21:11:12 +00007
Mike Kleinc0bd9f92019-04-23 12:05:21 -05008#include "include/utils/SkRandom.h"
9#include "src/core/SkGeometry.h"
10#include "src/core/SkPointPriv.h"
11#include "tests/Test.h"
Hal Canary8a001442018-09-19 11:31:27 -040012
Chris Dalton91982ee2017-07-14 14:04:52 -060013#include <array>
Chris Daltonfc31be42017-11-08 17:04:47 -070014#include <numeric>
reed@android.comd8730ea2009-02-27 22:06:06 +000015
reed@google.com6fc321a2011-07-27 13:54:36 +000016static bool nearly_equal(const SkPoint& a, const SkPoint& b) {
17 return SkScalarNearlyEqual(a.fX, b.fX) && SkScalarNearlyEqual(a.fY, b.fY);
18}
19
reed@google.com087d5aa2012-02-29 20:59:24 +000020static void testChopCubic(skiatest::Reporter* reporter) {
21 /*
22 Inspired by this test, which used to assert that the tValues had dups
rmistry@google.comd6176b02012-08-23 18:14:13 +000023
reed@google.com087d5aa2012-02-29 20:59:24 +000024 <path stroke="#202020" d="M0,0 C0,0 1,1 2190,5130 C2190,5070 2220,5010 2205,4980" />
25 */
26 const SkPoint src[] = {
27 { SkIntToScalar(2190), SkIntToScalar(5130) },
28 { SkIntToScalar(2190), SkIntToScalar(5070) },
29 { SkIntToScalar(2220), SkIntToScalar(5010) },
30 { SkIntToScalar(2205), SkIntToScalar(4980) },
31 };
32 SkPoint dst[13];
33 SkScalar tValues[3];
reed@google.comc256cd12012-02-29 21:57:36 +000034 // make sure we don't assert internally
reed@google.com087d5aa2012-02-29 20:59:24 +000035 int count = SkChopCubicAtMaxCurvature(src, dst, tValues);
caryclark@google.com42639cd2012-06-06 12:03:39 +000036 if (false) { // avoid bit rot, suppress warning
37 REPORTER_ASSERT(reporter, count);
38 }
Chris Dalton1208e0f2018-08-13 00:20:33 -060039 // Make sure src and dst can be the same pointer.
40 SkPoint pts[7];
41 for (int i = 0; i < 7; ++i) {
42 pts[i].set(i, i);
43 }
44 SkChopCubicAt(pts, pts, .5f);
45 for (int i = 0; i < 7; ++i) {
46 REPORTER_ASSERT(reporter, pts[i].fX == pts[i].fY);
47 REPORTER_ASSERT(reporter, pts[i].fX == i * .5f);
48 }
reed@google.com087d5aa2012-02-29 20:59:24 +000049}
50
reed40b7dd52015-03-20 06:01:08 -070051static void check_pairs(skiatest::Reporter* reporter, int index, SkScalar t, const char name[],
52 SkScalar x0, SkScalar y0, SkScalar x1, SkScalar y1) {
53 bool eq = SkScalarNearlyEqual(x0, x1) && SkScalarNearlyEqual(y0, y1);
54 if (!eq) {
55 SkDebugf("%s [%d %g] p0 [%10.8f %10.8f] p1 [%10.8f %10.8f]\n",
56 name, index, t, x0, y0, x1, y1);
57 REPORTER_ASSERT(reporter, eq);
58 }
59}
60
reed65cb2cd2015-03-19 10:18:47 -070061static void test_evalquadat(skiatest::Reporter* reporter) {
62 SkRandom rand;
63 for (int i = 0; i < 1000; ++i) {
64 SkPoint pts[3];
65 for (int j = 0; j < 3; ++j) {
66 pts[j].set(rand.nextSScalar1() * 100, rand.nextSScalar1() * 100);
67 }
reed65cb2cd2015-03-19 10:18:47 -070068 const SkScalar dt = SK_Scalar1 / 128;
reed40b7dd52015-03-20 06:01:08 -070069 SkScalar t = dt;
70 for (int j = 1; j < 128; ++j) {
reed65cb2cd2015-03-19 10:18:47 -070071 SkPoint r0;
72 SkEvalQuadAt(pts, t, &r0);
73 SkPoint r1 = SkEvalQuadAt(pts, t);
reed40b7dd52015-03-20 06:01:08 -070074 check_pairs(reporter, i, t, "quad-pos", r0.fX, r0.fY, r1.fX, r1.fY);
halcanary9d524f22016-03-29 09:03:52 -070075
reed40b7dd52015-03-20 06:01:08 -070076 SkVector v0;
halcanary96fcdcc2015-08-27 07:41:13 -070077 SkEvalQuadAt(pts, t, nullptr, &v0);
reed40b7dd52015-03-20 06:01:08 -070078 SkVector v1 = SkEvalQuadTangentAt(pts, t);
79 check_pairs(reporter, i, t, "quad-tan", v0.fX, v0.fY, v1.fX, v1.fY);
reed40b7dd52015-03-20 06:01:08 -070080
reed65cb2cd2015-03-19 10:18:47 -070081 t += dt;
82 }
83 }
84}
85
reedb6402032015-03-20 13:23:43 -070086static void test_conic_eval_pos(skiatest::Reporter* reporter, const SkConic& conic, SkScalar t) {
87 SkPoint p0, p1;
halcanary96fcdcc2015-08-27 07:41:13 -070088 conic.evalAt(t, &p0, nullptr);
reedb6402032015-03-20 13:23:43 -070089 p1 = conic.evalAt(t);
90 check_pairs(reporter, 0, t, "conic-pos", p0.fX, p0.fY, p1.fX, p1.fY);
91}
92
93static void test_conic_eval_tan(skiatest::Reporter* reporter, const SkConic& conic, SkScalar t) {
94 SkVector v0, v1;
halcanary96fcdcc2015-08-27 07:41:13 -070095 conic.evalAt(t, nullptr, &v0);
reedb6402032015-03-20 13:23:43 -070096 v1 = conic.evalTangentAt(t);
97 check_pairs(reporter, 0, t, "conic-tan", v0.fX, v0.fY, v1.fX, v1.fY);
98}
99
reedb6402032015-03-20 13:23:43 -0700100static void test_conic(skiatest::Reporter* reporter) {
101 SkRandom rand;
102 for (int i = 0; i < 1000; ++i) {
103 SkPoint pts[3];
104 for (int j = 0; j < 3; ++j) {
105 pts[j].set(rand.nextSScalar1() * 100, rand.nextSScalar1() * 100);
106 }
107 for (int k = 0; k < 10; ++k) {
108 SkScalar w = rand.nextUScalar1() * 2;
109 SkConic conic(pts, w);
reedb6402032015-03-20 13:23:43 -0700110
111 const SkScalar dt = SK_Scalar1 / 128;
112 SkScalar t = dt;
113 for (int j = 1; j < 128; ++j) {
114 test_conic_eval_pos(reporter, conic, t);
115 test_conic_eval_tan(reporter, conic, t);
116 t += dt;
117 }
118 }
119 }
120}
121
caryclark45398df2015-08-25 13:19:06 -0700122static void test_quad_tangents(skiatest::Reporter* reporter) {
123 SkPoint pts[] = {
124 {10, 20}, {10, 20}, {20, 30},
125 {10, 20}, {15, 25}, {20, 30},
126 {10, 20}, {20, 30}, {20, 30},
127 };
128 int count = (int) SK_ARRAY_COUNT(pts) / 3;
129 for (int index = 0; index < count; ++index) {
130 SkConic conic(&pts[index * 3], 0.707f);
131 SkVector start = SkEvalQuadTangentAt(&pts[index * 3], 0);
132 SkVector mid = SkEvalQuadTangentAt(&pts[index * 3], .5f);
133 SkVector end = SkEvalQuadTangentAt(&pts[index * 3], 1);
134 REPORTER_ASSERT(reporter, start.fX && start.fY);
135 REPORTER_ASSERT(reporter, mid.fX && mid.fY);
136 REPORTER_ASSERT(reporter, end.fX && end.fY);
137 REPORTER_ASSERT(reporter, SkScalarNearlyZero(start.cross(mid)));
138 REPORTER_ASSERT(reporter, SkScalarNearlyZero(mid.cross(end)));
139 }
140}
141
142static void test_conic_tangents(skiatest::Reporter* reporter) {
143 SkPoint pts[] = {
144 { 10, 20}, {10, 20}, {20, 30},
145 { 10, 20}, {15, 25}, {20, 30},
146 { 10, 20}, {20, 30}, {20, 30}
147 };
148 int count = (int) SK_ARRAY_COUNT(pts) / 3;
149 for (int index = 0; index < count; ++index) {
150 SkConic conic(&pts[index * 3], 0.707f);
151 SkVector start = conic.evalTangentAt(0);
152 SkVector mid = conic.evalTangentAt(.5f);
153 SkVector end = conic.evalTangentAt(1);
154 REPORTER_ASSERT(reporter, start.fX && start.fY);
155 REPORTER_ASSERT(reporter, mid.fX && mid.fY);
156 REPORTER_ASSERT(reporter, end.fX && end.fY);
157 REPORTER_ASSERT(reporter, SkScalarNearlyZero(start.cross(mid)));
158 REPORTER_ASSERT(reporter, SkScalarNearlyZero(mid.cross(end)));
159 }
160}
161
reedb1b12f82016-07-13 10:56:53 -0700162static void test_this_conic_to_quad(skiatest::Reporter* r, const SkPoint pts[3], SkScalar w) {
163 SkAutoConicToQuads quadder;
164 const SkPoint* qpts = quadder.computeQuads(pts, w, 0.25);
165 const int qcount = quadder.countQuads();
166 const int pcount = qcount * 2 + 1;
167
Cary Clarkdf429f32017-11-08 11:44:31 -0500168 REPORTER_ASSERT(r, SkPointPriv::AreFinite(qpts, pcount));
reedb1b12f82016-07-13 10:56:53 -0700169}
170
171/**
172 * We need to ensure that when a conic is approximated by quads, that we always return finite
173 * values in the quads.
174 *
175 * Inspired by crbug_627414
176 */
177static void test_conic_to_quads(skiatest::Reporter* reporter) {
178 const SkPoint triples[] = {
179 { 0, 0 }, { 1, 0 }, { 1, 1 },
msarett16ef4652016-07-13 13:08:44 -0700180 { 0, 0 }, { 3.58732e-43f, 2.72084f }, { 3.00392f, 3.00392f },
reedb1b12f82016-07-13 10:56:53 -0700181 { 0, 0 }, { 100000, 0 }, { 100000, 100000 },
182 { 0, 0 }, { 1e30f, 0 }, { 1e30f, 1e30f },
183 };
184 const int N = sizeof(triples) / sizeof(SkPoint);
185
186 for (int i = 0; i < N; i += 3) {
187 const SkPoint* pts = &triples[i];
188
reedb1b12f82016-07-13 10:56:53 -0700189 SkScalar w = 1e30f;
190 do {
191 w *= 2;
192 test_this_conic_to_quad(reporter, pts, w);
193 } while (SkScalarIsFinite(w));
194 test_this_conic_to_quad(reporter, pts, SK_ScalarNaN);
195 }
196}
197
caryclark45398df2015-08-25 13:19:06 -0700198static void test_cubic_tangents(skiatest::Reporter* reporter) {
199 SkPoint pts[] = {
200 { 10, 20}, {10, 20}, {20, 30}, {30, 40},
201 { 10, 20}, {15, 25}, {20, 30}, {30, 40},
202 { 10, 20}, {20, 30}, {30, 40}, {30, 40},
203 };
204 int count = (int) SK_ARRAY_COUNT(pts) / 4;
205 for (int index = 0; index < count; ++index) {
206 SkConic conic(&pts[index * 3], 0.707f);
207 SkVector start, mid, end;
halcanary96fcdcc2015-08-27 07:41:13 -0700208 SkEvalCubicAt(&pts[index * 4], 0, nullptr, &start, nullptr);
209 SkEvalCubicAt(&pts[index * 4], .5f, nullptr, &mid, nullptr);
210 SkEvalCubicAt(&pts[index * 4], 1, nullptr, &end, nullptr);
caryclark45398df2015-08-25 13:19:06 -0700211 REPORTER_ASSERT(reporter, start.fX && start.fY);
212 REPORTER_ASSERT(reporter, mid.fX && mid.fY);
213 REPORTER_ASSERT(reporter, end.fX && end.fY);
214 REPORTER_ASSERT(reporter, SkScalarNearlyZero(start.cross(mid)));
215 REPORTER_ASSERT(reporter, SkScalarNearlyZero(mid.cross(end)));
216 }
217}
218
Chris Dalton91982ee2017-07-14 14:04:52 -0600219static void check_cubic_type(skiatest::Reporter* reporter,
Chris Daltonfc31be42017-11-08 17:04:47 -0700220 const std::array<SkPoint, 4>& bezierPoints, SkCubicType expectedType,
221 bool undefined = false) {
222 // Classify the cubic even if the results will be undefined: check for crashes and asserts.
Chris Dalton91982ee2017-07-14 14:04:52 -0600223 SkCubicType actualType = SkClassifyCubic(bezierPoints.data());
Chris Daltonfc31be42017-11-08 17:04:47 -0700224 if (!undefined) {
225 REPORTER_ASSERT(reporter, actualType == expectedType);
226 }
227}
228
229static void check_cubic_around_rect(skiatest::Reporter* reporter,
230 float x1, float y1, float x2, float y2,
231 bool undefined = false) {
232 static constexpr SkCubicType expectations[24] = {
233 SkCubicType::kLoop,
234 SkCubicType::kCuspAtInfinity,
235 SkCubicType::kLocalCusp,
236 SkCubicType::kLocalCusp,
237 SkCubicType::kCuspAtInfinity,
238 SkCubicType::kLoop,
239 SkCubicType::kCuspAtInfinity,
240 SkCubicType::kLoop,
241 SkCubicType::kCuspAtInfinity,
242 SkCubicType::kLoop,
243 SkCubicType::kLocalCusp,
244 SkCubicType::kLocalCusp,
245 SkCubicType::kLocalCusp,
246 SkCubicType::kLocalCusp,
247 SkCubicType::kLoop,
248 SkCubicType::kCuspAtInfinity,
249 SkCubicType::kLoop,
250 SkCubicType::kCuspAtInfinity,
251 SkCubicType::kLoop,
252 SkCubicType::kCuspAtInfinity,
253 SkCubicType::kLocalCusp,
254 SkCubicType::kLocalCusp,
255 SkCubicType::kCuspAtInfinity,
256 SkCubicType::kLoop,
257 };
258 SkPoint points[] = {{x1, y1}, {x2, y1}, {x2, y2}, {x1, y2}};
259 std::array<SkPoint, 4> bezier;
260 for (int i=0; i < 4; ++i) {
261 bezier[0] = points[i];
262 for (int j=0; j < 3; ++j) {
263 int jidx = (j < i) ? j : j+1;
264 bezier[1] = points[jidx];
265 for (int k=0, kidx=0; k < 2; ++k, ++kidx) {
266 for (int n = 0; n < 2; ++n) {
267 kidx = (kidx == i || kidx == jidx) ? kidx+1 : kidx;
268 }
269 bezier[2] = points[kidx];
270 for (int l = 0; l < 4; ++l) {
271 if (l != i && l != jidx && l != kidx) {
272 bezier[3] = points[l];
273 break;
274 }
275 }
276 check_cubic_type(reporter, bezier, expectations[i*6 + j*2 + k], undefined);
277 }
278 }
279 }
280 for (int i=0; i < 4; ++i) {
281 bezier[0] = points[i];
282 for (int j=0; j < 3; ++j) {
283 int jidx = (j < i) ? j : j+1;
284 bezier[1] = points[jidx];
285 bezier[2] = points[jidx];
286 for (int k=0, kidx=0; k < 2; ++k, ++kidx) {
287 for (int n = 0; n < 2; ++n) {
288 kidx = (kidx == i || kidx == jidx) ? kidx+1 : kidx;
289 }
290 bezier[3] = points[kidx];
291 check_cubic_type(reporter, bezier, SkCubicType::kSerpentine, undefined);
292 }
293 }
294 }
Chris Dalton91982ee2017-07-14 14:04:52 -0600295}
296
297static void test_classify_cubic(skiatest::Reporter* reporter) {
298 check_cubic_type(reporter, {{{149.325f, 107.705f}, {149.325f, 103.783f},
299 {151.638f, 100.127f}, {156.263f, 96.736f}}},
Chris Dalton29011a22017-09-28 12:08:33 -0600300 SkCubicType::kSerpentine);
Chris Dalton91982ee2017-07-14 14:04:52 -0600301 check_cubic_type(reporter, {{{225.694f, 223.15f}, {209.831f, 224.837f},
302 {195.994f, 230.237f}, {184.181f, 239.35f}}},
Chris Dalton29011a22017-09-28 12:08:33 -0600303 SkCubicType::kSerpentine);
Chris Dalton91982ee2017-07-14 14:04:52 -0600304 check_cubic_type(reporter, {{{4.873f, 5.581f}, {5.083f, 5.2783f},
305 {5.182f, 4.8593f}, {5.177f, 4.3242f}}},
306 SkCubicType::kSerpentine);
Chris Daltonfc31be42017-11-08 17:04:47 -0700307 check_cubic_around_rect(reporter, 0, 0, 1, 1);
308 check_cubic_around_rect(reporter,
309 -std::numeric_limits<float>::max(),
310 -std::numeric_limits<float>::max(),
311 +std::numeric_limits<float>::max(),
312 +std::numeric_limits<float>::max());
313 check_cubic_around_rect(reporter, 1, 1,
314 +std::numeric_limits<float>::min(),
315 +std::numeric_limits<float>::max());
316 check_cubic_around_rect(reporter,
317 -std::numeric_limits<float>::min(),
318 -std::numeric_limits<float>::min(),
319 +std::numeric_limits<float>::min(),
320 +std::numeric_limits<float>::min());
321 check_cubic_around_rect(reporter, +1, -std::numeric_limits<float>::min(), -1, -1);
322 check_cubic_around_rect(reporter,
323 -std::numeric_limits<float>::infinity(),
324 -std::numeric_limits<float>::infinity(),
325 +std::numeric_limits<float>::infinity(),
326 +std::numeric_limits<float>::infinity(),
327 true);
328 check_cubic_around_rect(reporter, 0, 0, 1, +std::numeric_limits<float>::infinity(), true);
329 check_cubic_around_rect(reporter,
330 -std::numeric_limits<float>::quiet_NaN(),
331 -std::numeric_limits<float>::quiet_NaN(),
332 +std::numeric_limits<float>::quiet_NaN(),
333 +std::numeric_limits<float>::quiet_NaN(),
334 true);
335 check_cubic_around_rect(reporter, 0, 0, 1, +std::numeric_limits<float>::quiet_NaN(), true);
Chris Dalton91982ee2017-07-14 14:04:52 -0600336}
337
Cary Clarkdb160012018-08-31 15:07:51 -0400338static void test_cubic_cusps(skiatest::Reporter* reporter) {
339 std::array<SkPoint, 4> noCusps[] = {
340 {{{0, 0}, {1, 1}, {2, 2}, {3, 3}}},
341 {{{0, 0}, {1, 0}, {1, 1}, {0, 1}}},
342 {{{0, 0}, {1, 0}, {2, 1}, {2, 2}}},
343 {{{0, 0}, {1, 0}, {1, 1}, {2, 1}}},
344 };
345 for (auto noCusp : noCusps) {
346 REPORTER_ASSERT(reporter, SkFindCubicCusp(noCusp.data()) < 0);
347 }
348 std::array<SkPoint, 4> cusps[] = {
349 {{{0, 0}, {1, 1}, {1, 0}, {0, 1}}},
350 {{{0, 0}, {1, 1}, {0, 1}, {1, 0}}},
351 {{{0, 1}, {1, 0}, {0, 0}, {1, 1}}},
352 {{{0, 1}, {1, 0}, {1, 1}, {0, 0}}},
353 };
354 for (auto cusp : cusps) {
355 REPORTER_ASSERT(reporter, SkFindCubicCusp(cusp.data()) > 0);
356 }
357}
358
tfarina@chromium.orge4fafb12013-12-12 21:11:12 +0000359DEF_TEST(Geometry, reporter) {
Chris Dalton1208e0f2018-08-13 00:20:33 -0600360 SkPoint pts[5];
reed@android.comd8730ea2009-02-27 22:06:06 +0000361
362 pts[0].set(0, 0);
363 pts[1].set(100, 50);
364 pts[2].set(0, 100);
365
Chris Dalton1208e0f2018-08-13 00:20:33 -0600366 int count = SkChopQuadAtMaxCurvature(pts, pts); // Ensure src and dst can be the same pointer.
reed@android.comd8730ea2009-02-27 22:06:06 +0000367 REPORTER_ASSERT(reporter, count == 1 || count == 2);
reed@google.com6fc321a2011-07-27 13:54:36 +0000368
369 pts[0].set(0, 0);
reeddaee7ea2015-03-26 20:22:33 -0700370 pts[1].set(3, 0);
371 pts[2].set(3, 3);
Chris Dalton1208e0f2018-08-13 00:20:33 -0600372 SkConvertQuadToCubic(pts, pts);
reed@google.com6fc321a2011-07-27 13:54:36 +0000373 const SkPoint cubic[] = {
reeddaee7ea2015-03-26 20:22:33 -0700374 { 0, 0, }, { 2, 0, }, { 3, 1, }, { 3, 3 },
reed@google.com6fc321a2011-07-27 13:54:36 +0000375 };
376 for (int i = 0; i < 4; ++i) {
Chris Dalton1208e0f2018-08-13 00:20:33 -0600377 REPORTER_ASSERT(reporter, nearly_equal(cubic[i], pts[i]));
reed@google.com6fc321a2011-07-27 13:54:36 +0000378 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000379
reed@google.com087d5aa2012-02-29 20:59:24 +0000380 testChopCubic(reporter);
reed65cb2cd2015-03-19 10:18:47 -0700381 test_evalquadat(reporter);
reedb6402032015-03-20 13:23:43 -0700382 test_conic(reporter);
caryclark45398df2015-08-25 13:19:06 -0700383 test_cubic_tangents(reporter);
384 test_quad_tangents(reporter);
385 test_conic_tangents(reporter);
reedb1b12f82016-07-13 10:56:53 -0700386 test_conic_to_quads(reporter);
Chris Dalton91982ee2017-07-14 14:04:52 -0600387 test_classify_cubic(reporter);
Cary Clarkdb160012018-08-31 15:07:51 -0400388 test_cubic_cusps(reporter);
reed@android.comd8730ea2009-02-27 22:06:06 +0000389}