blob: 1cf9d407fbf1697118dc4d8f500db6d1edbaadd7 [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
reed@android.comd8730ea2009-02-27 22:06:06 +00008#include "SkGeometry.h"
Cary Clarkdf429f32017-11-08 11:44:31 -05009#include "SkPointPriv.h"
reed65cb2cd2015-03-19 10:18:47 -070010#include "SkRandom.h"
Cary Clarkdf429f32017-11-08 11:44:31 -050011#include "Test.h"
Chris Dalton91982ee2017-07-14 14:04:52 -060012#include <array>
Chris Daltonfc31be42017-11-08 17:04:47 -070013#include <numeric>
reed@android.comd8730ea2009-02-27 22:06:06 +000014
reed@google.com6fc321a2011-07-27 13:54:36 +000015static bool nearly_equal(const SkPoint& a, const SkPoint& b) {
16 return SkScalarNearlyEqual(a.fX, b.fX) && SkScalarNearlyEqual(a.fY, b.fY);
17}
18
reed@google.com087d5aa2012-02-29 20:59:24 +000019static void testChopCubic(skiatest::Reporter* reporter) {
20 /*
21 Inspired by this test, which used to assert that the tValues had dups
rmistry@google.comd6176b02012-08-23 18:14:13 +000022
reed@google.com087d5aa2012-02-29 20:59:24 +000023 <path stroke="#202020" d="M0,0 C0,0 1,1 2190,5130 C2190,5070 2220,5010 2205,4980" />
24 */
25 const SkPoint src[] = {
26 { SkIntToScalar(2190), SkIntToScalar(5130) },
27 { SkIntToScalar(2190), SkIntToScalar(5070) },
28 { SkIntToScalar(2220), SkIntToScalar(5010) },
29 { SkIntToScalar(2205), SkIntToScalar(4980) },
30 };
31 SkPoint dst[13];
32 SkScalar tValues[3];
reed@google.comc256cd12012-02-29 21:57:36 +000033 // make sure we don't assert internally
reed@google.com087d5aa2012-02-29 20:59:24 +000034 int count = SkChopCubicAtMaxCurvature(src, dst, tValues);
caryclark@google.com42639cd2012-06-06 12:03:39 +000035 if (false) { // avoid bit rot, suppress warning
36 REPORTER_ASSERT(reporter, count);
37 }
Chris Dalton1208e0f2018-08-13 00:20:33 -060038 // Make sure src and dst can be the same pointer.
39 SkPoint pts[7];
40 for (int i = 0; i < 7; ++i) {
41 pts[i].set(i, i);
42 }
43 SkChopCubicAt(pts, pts, .5f);
44 for (int i = 0; i < 7; ++i) {
45 REPORTER_ASSERT(reporter, pts[i].fX == pts[i].fY);
46 REPORTER_ASSERT(reporter, pts[i].fX == i * .5f);
47 }
reed@google.com087d5aa2012-02-29 20:59:24 +000048}
49
reed40b7dd52015-03-20 06:01:08 -070050static void check_pairs(skiatest::Reporter* reporter, int index, SkScalar t, const char name[],
51 SkScalar x0, SkScalar y0, SkScalar x1, SkScalar y1) {
52 bool eq = SkScalarNearlyEqual(x0, x1) && SkScalarNearlyEqual(y0, y1);
53 if (!eq) {
54 SkDebugf("%s [%d %g] p0 [%10.8f %10.8f] p1 [%10.8f %10.8f]\n",
55 name, index, t, x0, y0, x1, y1);
56 REPORTER_ASSERT(reporter, eq);
57 }
58}
59
reed65cb2cd2015-03-19 10:18:47 -070060static void test_evalquadat(skiatest::Reporter* reporter) {
61 SkRandom rand;
62 for (int i = 0; i < 1000; ++i) {
63 SkPoint pts[3];
64 for (int j = 0; j < 3; ++j) {
65 pts[j].set(rand.nextSScalar1() * 100, rand.nextSScalar1() * 100);
66 }
reed65cb2cd2015-03-19 10:18:47 -070067 const SkScalar dt = SK_Scalar1 / 128;
reed40b7dd52015-03-20 06:01:08 -070068 SkScalar t = dt;
69 for (int j = 1; j < 128; ++j) {
reed65cb2cd2015-03-19 10:18:47 -070070 SkPoint r0;
71 SkEvalQuadAt(pts, t, &r0);
72 SkPoint r1 = SkEvalQuadAt(pts, t);
reed40b7dd52015-03-20 06:01:08 -070073 check_pairs(reporter, i, t, "quad-pos", r0.fX, r0.fY, r1.fX, r1.fY);
halcanary9d524f22016-03-29 09:03:52 -070074
reed40b7dd52015-03-20 06:01:08 -070075 SkVector v0;
halcanary96fcdcc2015-08-27 07:41:13 -070076 SkEvalQuadAt(pts, t, nullptr, &v0);
reed40b7dd52015-03-20 06:01:08 -070077 SkVector v1 = SkEvalQuadTangentAt(pts, t);
78 check_pairs(reporter, i, t, "quad-tan", v0.fX, v0.fY, v1.fX, v1.fY);
reed40b7dd52015-03-20 06:01:08 -070079
reed65cb2cd2015-03-19 10:18:47 -070080 t += dt;
81 }
82 }
83}
84
reedb6402032015-03-20 13:23:43 -070085static void test_conic_eval_pos(skiatest::Reporter* reporter, const SkConic& conic, SkScalar t) {
86 SkPoint p0, p1;
halcanary96fcdcc2015-08-27 07:41:13 -070087 conic.evalAt(t, &p0, nullptr);
reedb6402032015-03-20 13:23:43 -070088 p1 = conic.evalAt(t);
89 check_pairs(reporter, 0, t, "conic-pos", p0.fX, p0.fY, p1.fX, p1.fY);
90}
91
92static void test_conic_eval_tan(skiatest::Reporter* reporter, const SkConic& conic, SkScalar t) {
93 SkVector v0, v1;
halcanary96fcdcc2015-08-27 07:41:13 -070094 conic.evalAt(t, nullptr, &v0);
reedb6402032015-03-20 13:23:43 -070095 v1 = conic.evalTangentAt(t);
96 check_pairs(reporter, 0, t, "conic-tan", v0.fX, v0.fY, v1.fX, v1.fY);
97}
98
reedb6402032015-03-20 13:23:43 -070099static void test_conic(skiatest::Reporter* reporter) {
100 SkRandom rand;
101 for (int i = 0; i < 1000; ++i) {
102 SkPoint pts[3];
103 for (int j = 0; j < 3; ++j) {
104 pts[j].set(rand.nextSScalar1() * 100, rand.nextSScalar1() * 100);
105 }
106 for (int k = 0; k < 10; ++k) {
107 SkScalar w = rand.nextUScalar1() * 2;
108 SkConic conic(pts, w);
reedb6402032015-03-20 13:23:43 -0700109
110 const SkScalar dt = SK_Scalar1 / 128;
111 SkScalar t = dt;
112 for (int j = 1; j < 128; ++j) {
113 test_conic_eval_pos(reporter, conic, t);
114 test_conic_eval_tan(reporter, conic, t);
115 t += dt;
116 }
117 }
118 }
119}
120
caryclark45398df2015-08-25 13:19:06 -0700121static void test_quad_tangents(skiatest::Reporter* reporter) {
122 SkPoint pts[] = {
123 {10, 20}, {10, 20}, {20, 30},
124 {10, 20}, {15, 25}, {20, 30},
125 {10, 20}, {20, 30}, {20, 30},
126 };
127 int count = (int) SK_ARRAY_COUNT(pts) / 3;
128 for (int index = 0; index < count; ++index) {
129 SkConic conic(&pts[index * 3], 0.707f);
130 SkVector start = SkEvalQuadTangentAt(&pts[index * 3], 0);
131 SkVector mid = SkEvalQuadTangentAt(&pts[index * 3], .5f);
132 SkVector end = SkEvalQuadTangentAt(&pts[index * 3], 1);
133 REPORTER_ASSERT(reporter, start.fX && start.fY);
134 REPORTER_ASSERT(reporter, mid.fX && mid.fY);
135 REPORTER_ASSERT(reporter, end.fX && end.fY);
136 REPORTER_ASSERT(reporter, SkScalarNearlyZero(start.cross(mid)));
137 REPORTER_ASSERT(reporter, SkScalarNearlyZero(mid.cross(end)));
138 }
139}
140
141static void test_conic_tangents(skiatest::Reporter* reporter) {
142 SkPoint pts[] = {
143 { 10, 20}, {10, 20}, {20, 30},
144 { 10, 20}, {15, 25}, {20, 30},
145 { 10, 20}, {20, 30}, {20, 30}
146 };
147 int count = (int) SK_ARRAY_COUNT(pts) / 3;
148 for (int index = 0; index < count; ++index) {
149 SkConic conic(&pts[index * 3], 0.707f);
150 SkVector start = conic.evalTangentAt(0);
151 SkVector mid = conic.evalTangentAt(.5f);
152 SkVector end = conic.evalTangentAt(1);
153 REPORTER_ASSERT(reporter, start.fX && start.fY);
154 REPORTER_ASSERT(reporter, mid.fX && mid.fY);
155 REPORTER_ASSERT(reporter, end.fX && end.fY);
156 REPORTER_ASSERT(reporter, SkScalarNearlyZero(start.cross(mid)));
157 REPORTER_ASSERT(reporter, SkScalarNearlyZero(mid.cross(end)));
158 }
159}
160
reedb1b12f82016-07-13 10:56:53 -0700161static void test_this_conic_to_quad(skiatest::Reporter* r, const SkPoint pts[3], SkScalar w) {
162 SkAutoConicToQuads quadder;
163 const SkPoint* qpts = quadder.computeQuads(pts, w, 0.25);
164 const int qcount = quadder.countQuads();
165 const int pcount = qcount * 2 + 1;
166
Cary Clarkdf429f32017-11-08 11:44:31 -0500167 REPORTER_ASSERT(r, SkPointPriv::AreFinite(qpts, pcount));
reedb1b12f82016-07-13 10:56:53 -0700168}
169
170/**
171 * We need to ensure that when a conic is approximated by quads, that we always return finite
172 * values in the quads.
173 *
174 * Inspired by crbug_627414
175 */
176static void test_conic_to_quads(skiatest::Reporter* reporter) {
177 const SkPoint triples[] = {
178 { 0, 0 }, { 1, 0 }, { 1, 1 },
msarett16ef4652016-07-13 13:08:44 -0700179 { 0, 0 }, { 3.58732e-43f, 2.72084f }, { 3.00392f, 3.00392f },
reedb1b12f82016-07-13 10:56:53 -0700180 { 0, 0 }, { 100000, 0 }, { 100000, 100000 },
181 { 0, 0 }, { 1e30f, 0 }, { 1e30f, 1e30f },
182 };
183 const int N = sizeof(triples) / sizeof(SkPoint);
184
185 for (int i = 0; i < N; i += 3) {
186 const SkPoint* pts = &triples[i];
187
188 SkRect bounds;
189 bounds.set(pts, 3);
190
191 SkScalar w = 1e30f;
192 do {
193 w *= 2;
194 test_this_conic_to_quad(reporter, pts, w);
195 } while (SkScalarIsFinite(w));
196 test_this_conic_to_quad(reporter, pts, SK_ScalarNaN);
197 }
198}
199
caryclark45398df2015-08-25 13:19:06 -0700200static void test_cubic_tangents(skiatest::Reporter* reporter) {
201 SkPoint pts[] = {
202 { 10, 20}, {10, 20}, {20, 30}, {30, 40},
203 { 10, 20}, {15, 25}, {20, 30}, {30, 40},
204 { 10, 20}, {20, 30}, {30, 40}, {30, 40},
205 };
206 int count = (int) SK_ARRAY_COUNT(pts) / 4;
207 for (int index = 0; index < count; ++index) {
208 SkConic conic(&pts[index * 3], 0.707f);
209 SkVector start, mid, end;
halcanary96fcdcc2015-08-27 07:41:13 -0700210 SkEvalCubicAt(&pts[index * 4], 0, nullptr, &start, nullptr);
211 SkEvalCubicAt(&pts[index * 4], .5f, nullptr, &mid, nullptr);
212 SkEvalCubicAt(&pts[index * 4], 1, nullptr, &end, nullptr);
caryclark45398df2015-08-25 13:19:06 -0700213 REPORTER_ASSERT(reporter, start.fX && start.fY);
214 REPORTER_ASSERT(reporter, mid.fX && mid.fY);
215 REPORTER_ASSERT(reporter, end.fX && end.fY);
216 REPORTER_ASSERT(reporter, SkScalarNearlyZero(start.cross(mid)));
217 REPORTER_ASSERT(reporter, SkScalarNearlyZero(mid.cross(end)));
218 }
219}
220
Chris Dalton91982ee2017-07-14 14:04:52 -0600221static void check_cubic_type(skiatest::Reporter* reporter,
Chris Daltonfc31be42017-11-08 17:04:47 -0700222 const std::array<SkPoint, 4>& bezierPoints, SkCubicType expectedType,
223 bool undefined = false) {
224 // Classify the cubic even if the results will be undefined: check for crashes and asserts.
Chris Dalton91982ee2017-07-14 14:04:52 -0600225 SkCubicType actualType = SkClassifyCubic(bezierPoints.data());
Chris Daltonfc31be42017-11-08 17:04:47 -0700226 if (!undefined) {
227 REPORTER_ASSERT(reporter, actualType == expectedType);
228 }
229}
230
231static void check_cubic_around_rect(skiatest::Reporter* reporter,
232 float x1, float y1, float x2, float y2,
233 bool undefined = false) {
234 static constexpr SkCubicType expectations[24] = {
235 SkCubicType::kLoop,
236 SkCubicType::kCuspAtInfinity,
237 SkCubicType::kLocalCusp,
238 SkCubicType::kLocalCusp,
239 SkCubicType::kCuspAtInfinity,
240 SkCubicType::kLoop,
241 SkCubicType::kCuspAtInfinity,
242 SkCubicType::kLoop,
243 SkCubicType::kCuspAtInfinity,
244 SkCubicType::kLoop,
245 SkCubicType::kLocalCusp,
246 SkCubicType::kLocalCusp,
247 SkCubicType::kLocalCusp,
248 SkCubicType::kLocalCusp,
249 SkCubicType::kLoop,
250 SkCubicType::kCuspAtInfinity,
251 SkCubicType::kLoop,
252 SkCubicType::kCuspAtInfinity,
253 SkCubicType::kLoop,
254 SkCubicType::kCuspAtInfinity,
255 SkCubicType::kLocalCusp,
256 SkCubicType::kLocalCusp,
257 SkCubicType::kCuspAtInfinity,
258 SkCubicType::kLoop,
259 };
260 SkPoint points[] = {{x1, y1}, {x2, y1}, {x2, y2}, {x1, y2}};
261 std::array<SkPoint, 4> bezier;
262 for (int i=0; i < 4; ++i) {
263 bezier[0] = points[i];
264 for (int j=0; j < 3; ++j) {
265 int jidx = (j < i) ? j : j+1;
266 bezier[1] = points[jidx];
267 for (int k=0, kidx=0; k < 2; ++k, ++kidx) {
268 for (int n = 0; n < 2; ++n) {
269 kidx = (kidx == i || kidx == jidx) ? kidx+1 : kidx;
270 }
271 bezier[2] = points[kidx];
272 for (int l = 0; l < 4; ++l) {
273 if (l != i && l != jidx && l != kidx) {
274 bezier[3] = points[l];
275 break;
276 }
277 }
278 check_cubic_type(reporter, bezier, expectations[i*6 + j*2 + k], undefined);
279 }
280 }
281 }
282 for (int i=0; i < 4; ++i) {
283 bezier[0] = points[i];
284 for (int j=0; j < 3; ++j) {
285 int jidx = (j < i) ? j : j+1;
286 bezier[1] = points[jidx];
287 bezier[2] = points[jidx];
288 for (int k=0, kidx=0; k < 2; ++k, ++kidx) {
289 for (int n = 0; n < 2; ++n) {
290 kidx = (kidx == i || kidx == jidx) ? kidx+1 : kidx;
291 }
292 bezier[3] = points[kidx];
293 check_cubic_type(reporter, bezier, SkCubicType::kSerpentine, undefined);
294 }
295 }
296 }
Chris Dalton91982ee2017-07-14 14:04:52 -0600297}
298
299static void test_classify_cubic(skiatest::Reporter* reporter) {
300 check_cubic_type(reporter, {{{149.325f, 107.705f}, {149.325f, 103.783f},
301 {151.638f, 100.127f}, {156.263f, 96.736f}}},
Chris Dalton29011a22017-09-28 12:08:33 -0600302 SkCubicType::kSerpentine);
Chris Dalton91982ee2017-07-14 14:04:52 -0600303 check_cubic_type(reporter, {{{225.694f, 223.15f}, {209.831f, 224.837f},
304 {195.994f, 230.237f}, {184.181f, 239.35f}}},
Chris Dalton29011a22017-09-28 12:08:33 -0600305 SkCubicType::kSerpentine);
Chris Dalton91982ee2017-07-14 14:04:52 -0600306 check_cubic_type(reporter, {{{4.873f, 5.581f}, {5.083f, 5.2783f},
307 {5.182f, 4.8593f}, {5.177f, 4.3242f}}},
308 SkCubicType::kSerpentine);
Chris Daltonfc31be42017-11-08 17:04:47 -0700309 check_cubic_around_rect(reporter, 0, 0, 1, 1);
310 check_cubic_around_rect(reporter,
311 -std::numeric_limits<float>::max(),
312 -std::numeric_limits<float>::max(),
313 +std::numeric_limits<float>::max(),
314 +std::numeric_limits<float>::max());
315 check_cubic_around_rect(reporter, 1, 1,
316 +std::numeric_limits<float>::min(),
317 +std::numeric_limits<float>::max());
318 check_cubic_around_rect(reporter,
319 -std::numeric_limits<float>::min(),
320 -std::numeric_limits<float>::min(),
321 +std::numeric_limits<float>::min(),
322 +std::numeric_limits<float>::min());
323 check_cubic_around_rect(reporter, +1, -std::numeric_limits<float>::min(), -1, -1);
324 check_cubic_around_rect(reporter,
325 -std::numeric_limits<float>::infinity(),
326 -std::numeric_limits<float>::infinity(),
327 +std::numeric_limits<float>::infinity(),
328 +std::numeric_limits<float>::infinity(),
329 true);
330 check_cubic_around_rect(reporter, 0, 0, 1, +std::numeric_limits<float>::infinity(), true);
331 check_cubic_around_rect(reporter,
332 -std::numeric_limits<float>::quiet_NaN(),
333 -std::numeric_limits<float>::quiet_NaN(),
334 +std::numeric_limits<float>::quiet_NaN(),
335 +std::numeric_limits<float>::quiet_NaN(),
336 true);
337 check_cubic_around_rect(reporter, 0, 0, 1, +std::numeric_limits<float>::quiet_NaN(), true);
Chris Dalton91982ee2017-07-14 14:04:52 -0600338}
339
tfarina@chromium.orge4fafb12013-12-12 21:11:12 +0000340DEF_TEST(Geometry, reporter) {
Chris Dalton1208e0f2018-08-13 00:20:33 -0600341 SkPoint pts[5];
reed@android.comd8730ea2009-02-27 22:06:06 +0000342
343 pts[0].set(0, 0);
344 pts[1].set(100, 50);
345 pts[2].set(0, 100);
346
Chris Dalton1208e0f2018-08-13 00:20:33 -0600347 int count = SkChopQuadAtMaxCurvature(pts, pts); // Ensure src and dst can be the same pointer.
reed@android.comd8730ea2009-02-27 22:06:06 +0000348 REPORTER_ASSERT(reporter, count == 1 || count == 2);
reed@google.com6fc321a2011-07-27 13:54:36 +0000349
350 pts[0].set(0, 0);
reeddaee7ea2015-03-26 20:22:33 -0700351 pts[1].set(3, 0);
352 pts[2].set(3, 3);
Chris Dalton1208e0f2018-08-13 00:20:33 -0600353 SkConvertQuadToCubic(pts, pts);
reed@google.com6fc321a2011-07-27 13:54:36 +0000354 const SkPoint cubic[] = {
reeddaee7ea2015-03-26 20:22:33 -0700355 { 0, 0, }, { 2, 0, }, { 3, 1, }, { 3, 3 },
reed@google.com6fc321a2011-07-27 13:54:36 +0000356 };
357 for (int i = 0; i < 4; ++i) {
Chris Dalton1208e0f2018-08-13 00:20:33 -0600358 REPORTER_ASSERT(reporter, nearly_equal(cubic[i], pts[i]));
reed@google.com6fc321a2011-07-27 13:54:36 +0000359 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000360
reed@google.com087d5aa2012-02-29 20:59:24 +0000361 testChopCubic(reporter);
reed65cb2cd2015-03-19 10:18:47 -0700362 test_evalquadat(reporter);
reedb6402032015-03-20 13:23:43 -0700363 test_conic(reporter);
caryclark45398df2015-08-25 13:19:06 -0700364 test_cubic_tangents(reporter);
365 test_quad_tangents(reporter);
366 test_conic_tangents(reporter);
reedb1b12f82016-07-13 10:56:53 -0700367 test_conic_to_quads(reporter);
Chris Dalton91982ee2017-07-14 14:04:52 -0600368 test_classify_cubic(reporter);
reed@android.comd8730ea2009-02-27 22:06:06 +0000369}