blob: e434a7856fd311c55528e8e85e9376eced36969b [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 }
reed@google.com087d5aa2012-02-29 20:59:24 +000038}
39
reed40b7dd52015-03-20 06:01:08 -070040static void check_pairs(skiatest::Reporter* reporter, int index, SkScalar t, const char name[],
41 SkScalar x0, SkScalar y0, SkScalar x1, SkScalar y1) {
42 bool eq = SkScalarNearlyEqual(x0, x1) && SkScalarNearlyEqual(y0, y1);
43 if (!eq) {
44 SkDebugf("%s [%d %g] p0 [%10.8f %10.8f] p1 [%10.8f %10.8f]\n",
45 name, index, t, x0, y0, x1, y1);
46 REPORTER_ASSERT(reporter, eq);
47 }
48}
49
reed65cb2cd2015-03-19 10:18:47 -070050static void test_evalquadat(skiatest::Reporter* reporter) {
51 SkRandom rand;
52 for (int i = 0; i < 1000; ++i) {
53 SkPoint pts[3];
54 for (int j = 0; j < 3; ++j) {
55 pts[j].set(rand.nextSScalar1() * 100, rand.nextSScalar1() * 100);
56 }
reed65cb2cd2015-03-19 10:18:47 -070057 const SkScalar dt = SK_Scalar1 / 128;
reed40b7dd52015-03-20 06:01:08 -070058 SkScalar t = dt;
59 for (int j = 1; j < 128; ++j) {
reed65cb2cd2015-03-19 10:18:47 -070060 SkPoint r0;
61 SkEvalQuadAt(pts, t, &r0);
62 SkPoint r1 = SkEvalQuadAt(pts, t);
reed40b7dd52015-03-20 06:01:08 -070063 check_pairs(reporter, i, t, "quad-pos", r0.fX, r0.fY, r1.fX, r1.fY);
halcanary9d524f22016-03-29 09:03:52 -070064
reed40b7dd52015-03-20 06:01:08 -070065 SkVector v0;
halcanary96fcdcc2015-08-27 07:41:13 -070066 SkEvalQuadAt(pts, t, nullptr, &v0);
reed40b7dd52015-03-20 06:01:08 -070067 SkVector v1 = SkEvalQuadTangentAt(pts, t);
68 check_pairs(reporter, i, t, "quad-tan", v0.fX, v0.fY, v1.fX, v1.fY);
reed40b7dd52015-03-20 06:01:08 -070069
reed65cb2cd2015-03-19 10:18:47 -070070 t += dt;
71 }
72 }
73}
74
reedb6402032015-03-20 13:23:43 -070075static void test_conic_eval_pos(skiatest::Reporter* reporter, const SkConic& conic, SkScalar t) {
76 SkPoint p0, p1;
halcanary96fcdcc2015-08-27 07:41:13 -070077 conic.evalAt(t, &p0, nullptr);
reedb6402032015-03-20 13:23:43 -070078 p1 = conic.evalAt(t);
79 check_pairs(reporter, 0, t, "conic-pos", p0.fX, p0.fY, p1.fX, p1.fY);
80}
81
82static void test_conic_eval_tan(skiatest::Reporter* reporter, const SkConic& conic, SkScalar t) {
83 SkVector v0, v1;
halcanary96fcdcc2015-08-27 07:41:13 -070084 conic.evalAt(t, nullptr, &v0);
reedb6402032015-03-20 13:23:43 -070085 v1 = conic.evalTangentAt(t);
86 check_pairs(reporter, 0, t, "conic-tan", v0.fX, v0.fY, v1.fX, v1.fY);
87}
88
reedb6402032015-03-20 13:23:43 -070089static void test_conic(skiatest::Reporter* reporter) {
90 SkRandom rand;
91 for (int i = 0; i < 1000; ++i) {
92 SkPoint pts[3];
93 for (int j = 0; j < 3; ++j) {
94 pts[j].set(rand.nextSScalar1() * 100, rand.nextSScalar1() * 100);
95 }
96 for (int k = 0; k < 10; ++k) {
97 SkScalar w = rand.nextUScalar1() * 2;
98 SkConic conic(pts, w);
reedb6402032015-03-20 13:23:43 -070099
100 const SkScalar dt = SK_Scalar1 / 128;
101 SkScalar t = dt;
102 for (int j = 1; j < 128; ++j) {
103 test_conic_eval_pos(reporter, conic, t);
104 test_conic_eval_tan(reporter, conic, t);
105 t += dt;
106 }
107 }
108 }
109}
110
caryclark45398df2015-08-25 13:19:06 -0700111static void test_quad_tangents(skiatest::Reporter* reporter) {
112 SkPoint pts[] = {
113 {10, 20}, {10, 20}, {20, 30},
114 {10, 20}, {15, 25}, {20, 30},
115 {10, 20}, {20, 30}, {20, 30},
116 };
117 int count = (int) SK_ARRAY_COUNT(pts) / 3;
118 for (int index = 0; index < count; ++index) {
119 SkConic conic(&pts[index * 3], 0.707f);
120 SkVector start = SkEvalQuadTangentAt(&pts[index * 3], 0);
121 SkVector mid = SkEvalQuadTangentAt(&pts[index * 3], .5f);
122 SkVector end = SkEvalQuadTangentAt(&pts[index * 3], 1);
123 REPORTER_ASSERT(reporter, start.fX && start.fY);
124 REPORTER_ASSERT(reporter, mid.fX && mid.fY);
125 REPORTER_ASSERT(reporter, end.fX && end.fY);
126 REPORTER_ASSERT(reporter, SkScalarNearlyZero(start.cross(mid)));
127 REPORTER_ASSERT(reporter, SkScalarNearlyZero(mid.cross(end)));
128 }
129}
130
131static void test_conic_tangents(skiatest::Reporter* reporter) {
132 SkPoint pts[] = {
133 { 10, 20}, {10, 20}, {20, 30},
134 { 10, 20}, {15, 25}, {20, 30},
135 { 10, 20}, {20, 30}, {20, 30}
136 };
137 int count = (int) SK_ARRAY_COUNT(pts) / 3;
138 for (int index = 0; index < count; ++index) {
139 SkConic conic(&pts[index * 3], 0.707f);
140 SkVector start = conic.evalTangentAt(0);
141 SkVector mid = conic.evalTangentAt(.5f);
142 SkVector end = conic.evalTangentAt(1);
143 REPORTER_ASSERT(reporter, start.fX && start.fY);
144 REPORTER_ASSERT(reporter, mid.fX && mid.fY);
145 REPORTER_ASSERT(reporter, end.fX && end.fY);
146 REPORTER_ASSERT(reporter, SkScalarNearlyZero(start.cross(mid)));
147 REPORTER_ASSERT(reporter, SkScalarNearlyZero(mid.cross(end)));
148 }
149}
150
reedb1b12f82016-07-13 10:56:53 -0700151static void test_this_conic_to_quad(skiatest::Reporter* r, const SkPoint pts[3], SkScalar w) {
152 SkAutoConicToQuads quadder;
153 const SkPoint* qpts = quadder.computeQuads(pts, w, 0.25);
154 const int qcount = quadder.countQuads();
155 const int pcount = qcount * 2 + 1;
156
Cary Clarkdf429f32017-11-08 11:44:31 -0500157 REPORTER_ASSERT(r, SkPointPriv::AreFinite(qpts, pcount));
reedb1b12f82016-07-13 10:56:53 -0700158}
159
160/**
161 * We need to ensure that when a conic is approximated by quads, that we always return finite
162 * values in the quads.
163 *
164 * Inspired by crbug_627414
165 */
166static void test_conic_to_quads(skiatest::Reporter* reporter) {
167 const SkPoint triples[] = {
168 { 0, 0 }, { 1, 0 }, { 1, 1 },
msarett16ef4652016-07-13 13:08:44 -0700169 { 0, 0 }, { 3.58732e-43f, 2.72084f }, { 3.00392f, 3.00392f },
reedb1b12f82016-07-13 10:56:53 -0700170 { 0, 0 }, { 100000, 0 }, { 100000, 100000 },
171 { 0, 0 }, { 1e30f, 0 }, { 1e30f, 1e30f },
172 };
173 const int N = sizeof(triples) / sizeof(SkPoint);
174
175 for (int i = 0; i < N; i += 3) {
176 const SkPoint* pts = &triples[i];
177
178 SkRect bounds;
179 bounds.set(pts, 3);
180
181 SkScalar w = 1e30f;
182 do {
183 w *= 2;
184 test_this_conic_to_quad(reporter, pts, w);
185 } while (SkScalarIsFinite(w));
186 test_this_conic_to_quad(reporter, pts, SK_ScalarNaN);
187 }
188}
189
caryclark45398df2015-08-25 13:19:06 -0700190static void test_cubic_tangents(skiatest::Reporter* reporter) {
191 SkPoint pts[] = {
192 { 10, 20}, {10, 20}, {20, 30}, {30, 40},
193 { 10, 20}, {15, 25}, {20, 30}, {30, 40},
194 { 10, 20}, {20, 30}, {30, 40}, {30, 40},
195 };
196 int count = (int) SK_ARRAY_COUNT(pts) / 4;
197 for (int index = 0; index < count; ++index) {
198 SkConic conic(&pts[index * 3], 0.707f);
199 SkVector start, mid, end;
halcanary96fcdcc2015-08-27 07:41:13 -0700200 SkEvalCubicAt(&pts[index * 4], 0, nullptr, &start, nullptr);
201 SkEvalCubicAt(&pts[index * 4], .5f, nullptr, &mid, nullptr);
202 SkEvalCubicAt(&pts[index * 4], 1, nullptr, &end, nullptr);
caryclark45398df2015-08-25 13:19:06 -0700203 REPORTER_ASSERT(reporter, start.fX && start.fY);
204 REPORTER_ASSERT(reporter, mid.fX && mid.fY);
205 REPORTER_ASSERT(reporter, end.fX && end.fY);
206 REPORTER_ASSERT(reporter, SkScalarNearlyZero(start.cross(mid)));
207 REPORTER_ASSERT(reporter, SkScalarNearlyZero(mid.cross(end)));
208 }
209}
210
Chris Dalton91982ee2017-07-14 14:04:52 -0600211static void check_cubic_type(skiatest::Reporter* reporter,
Chris Daltonfc31be42017-11-08 17:04:47 -0700212 const std::array<SkPoint, 4>& bezierPoints, SkCubicType expectedType,
213 bool undefined = false) {
214 // Classify the cubic even if the results will be undefined: check for crashes and asserts.
Chris Dalton91982ee2017-07-14 14:04:52 -0600215 SkCubicType actualType = SkClassifyCubic(bezierPoints.data());
Chris Daltonfc31be42017-11-08 17:04:47 -0700216 if (!undefined) {
217 REPORTER_ASSERT(reporter, actualType == expectedType);
218 }
219}
220
221static void check_cubic_around_rect(skiatest::Reporter* reporter,
222 float x1, float y1, float x2, float y2,
223 bool undefined = false) {
224 static constexpr SkCubicType expectations[24] = {
225 SkCubicType::kLoop,
226 SkCubicType::kCuspAtInfinity,
227 SkCubicType::kLocalCusp,
228 SkCubicType::kLocalCusp,
229 SkCubicType::kCuspAtInfinity,
230 SkCubicType::kLoop,
231 SkCubicType::kCuspAtInfinity,
232 SkCubicType::kLoop,
233 SkCubicType::kCuspAtInfinity,
234 SkCubicType::kLoop,
235 SkCubicType::kLocalCusp,
236 SkCubicType::kLocalCusp,
237 SkCubicType::kLocalCusp,
238 SkCubicType::kLocalCusp,
239 SkCubicType::kLoop,
240 SkCubicType::kCuspAtInfinity,
241 SkCubicType::kLoop,
242 SkCubicType::kCuspAtInfinity,
243 SkCubicType::kLoop,
244 SkCubicType::kCuspAtInfinity,
245 SkCubicType::kLocalCusp,
246 SkCubicType::kLocalCusp,
247 SkCubicType::kCuspAtInfinity,
248 SkCubicType::kLoop,
249 };
250 SkPoint points[] = {{x1, y1}, {x2, y1}, {x2, y2}, {x1, y2}};
251 std::array<SkPoint, 4> bezier;
252 for (int i=0; i < 4; ++i) {
253 bezier[0] = points[i];
254 for (int j=0; j < 3; ++j) {
255 int jidx = (j < i) ? j : j+1;
256 bezier[1] = points[jidx];
257 for (int k=0, kidx=0; k < 2; ++k, ++kidx) {
258 for (int n = 0; n < 2; ++n) {
259 kidx = (kidx == i || kidx == jidx) ? kidx+1 : kidx;
260 }
261 bezier[2] = points[kidx];
262 for (int l = 0; l < 4; ++l) {
263 if (l != i && l != jidx && l != kidx) {
264 bezier[3] = points[l];
265 break;
266 }
267 }
268 check_cubic_type(reporter, bezier, expectations[i*6 + j*2 + k], undefined);
269 }
270 }
271 }
272 for (int i=0; i < 4; ++i) {
273 bezier[0] = points[i];
274 for (int j=0; j < 3; ++j) {
275 int jidx = (j < i) ? j : j+1;
276 bezier[1] = points[jidx];
277 bezier[2] = points[jidx];
278 for (int k=0, kidx=0; k < 2; ++k, ++kidx) {
279 for (int n = 0; n < 2; ++n) {
280 kidx = (kidx == i || kidx == jidx) ? kidx+1 : kidx;
281 }
282 bezier[3] = points[kidx];
283 check_cubic_type(reporter, bezier, SkCubicType::kSerpentine, undefined);
284 }
285 }
286 }
Chris Dalton91982ee2017-07-14 14:04:52 -0600287}
288
289static void test_classify_cubic(skiatest::Reporter* reporter) {
290 check_cubic_type(reporter, {{{149.325f, 107.705f}, {149.325f, 103.783f},
291 {151.638f, 100.127f}, {156.263f, 96.736f}}},
Chris Dalton29011a22017-09-28 12:08:33 -0600292 SkCubicType::kSerpentine);
Chris Dalton91982ee2017-07-14 14:04:52 -0600293 check_cubic_type(reporter, {{{225.694f, 223.15f}, {209.831f, 224.837f},
294 {195.994f, 230.237f}, {184.181f, 239.35f}}},
Chris Dalton29011a22017-09-28 12:08:33 -0600295 SkCubicType::kSerpentine);
Chris Dalton91982ee2017-07-14 14:04:52 -0600296 check_cubic_type(reporter, {{{4.873f, 5.581f}, {5.083f, 5.2783f},
297 {5.182f, 4.8593f}, {5.177f, 4.3242f}}},
298 SkCubicType::kSerpentine);
Chris Daltonfc31be42017-11-08 17:04:47 -0700299 check_cubic_around_rect(reporter, 0, 0, 1, 1);
300 check_cubic_around_rect(reporter,
301 -std::numeric_limits<float>::max(),
302 -std::numeric_limits<float>::max(),
303 +std::numeric_limits<float>::max(),
304 +std::numeric_limits<float>::max());
305 check_cubic_around_rect(reporter, 1, 1,
306 +std::numeric_limits<float>::min(),
307 +std::numeric_limits<float>::max());
308 check_cubic_around_rect(reporter,
309 -std::numeric_limits<float>::min(),
310 -std::numeric_limits<float>::min(),
311 +std::numeric_limits<float>::min(),
312 +std::numeric_limits<float>::min());
313 check_cubic_around_rect(reporter, +1, -std::numeric_limits<float>::min(), -1, -1);
314 check_cubic_around_rect(reporter,
315 -std::numeric_limits<float>::infinity(),
316 -std::numeric_limits<float>::infinity(),
317 +std::numeric_limits<float>::infinity(),
318 +std::numeric_limits<float>::infinity(),
319 true);
320 check_cubic_around_rect(reporter, 0, 0, 1, +std::numeric_limits<float>::infinity(), true);
321 check_cubic_around_rect(reporter,
322 -std::numeric_limits<float>::quiet_NaN(),
323 -std::numeric_limits<float>::quiet_NaN(),
324 +std::numeric_limits<float>::quiet_NaN(),
325 +std::numeric_limits<float>::quiet_NaN(),
326 true);
327 check_cubic_around_rect(reporter, 0, 0, 1, +std::numeric_limits<float>::quiet_NaN(), true);
Chris Dalton91982ee2017-07-14 14:04:52 -0600328}
329
tfarina@chromium.orge4fafb12013-12-12 21:11:12 +0000330DEF_TEST(Geometry, reporter) {
reed@android.comd8730ea2009-02-27 22:06:06 +0000331 SkPoint pts[3], dst[5];
332
333 pts[0].set(0, 0);
334 pts[1].set(100, 50);
335 pts[2].set(0, 100);
336
337 int count = SkChopQuadAtMaxCurvature(pts, dst);
338 REPORTER_ASSERT(reporter, count == 1 || count == 2);
reed@google.com6fc321a2011-07-27 13:54:36 +0000339
340 pts[0].set(0, 0);
reeddaee7ea2015-03-26 20:22:33 -0700341 pts[1].set(3, 0);
342 pts[2].set(3, 3);
reed@google.com6fc321a2011-07-27 13:54:36 +0000343 SkConvertQuadToCubic(pts, dst);
344 const SkPoint cubic[] = {
reeddaee7ea2015-03-26 20:22:33 -0700345 { 0, 0, }, { 2, 0, }, { 3, 1, }, { 3, 3 },
reed@google.com6fc321a2011-07-27 13:54:36 +0000346 };
347 for (int i = 0; i < 4; ++i) {
348 REPORTER_ASSERT(reporter, nearly_equal(cubic[i], dst[i]));
349 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000350
reed@google.com087d5aa2012-02-29 20:59:24 +0000351 testChopCubic(reporter);
reed65cb2cd2015-03-19 10:18:47 -0700352 test_evalquadat(reporter);
reedb6402032015-03-20 13:23:43 -0700353 test_conic(reporter);
caryclark45398df2015-08-25 13:19:06 -0700354 test_cubic_tangents(reporter);
355 test_quad_tangents(reporter);
356 test_conic_tangents(reporter);
reedb1b12f82016-07-13 10:56:53 -0700357 test_conic_to_quads(reporter);
Chris Dalton91982ee2017-07-14 14:04:52 -0600358 test_classify_cubic(reporter);
reed@android.comd8730ea2009-02-27 22:06:06 +0000359}