1 /*
2 * Copyright 2023 Google LLC
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/private/base/SkFloatingPoint.h"
9 #include "include/private/base/SkSpan_impl.h"
10 #include "src/base/SkBezierCurves.h"
11 #include "tests/Test.h"
12
13 #include <string>
14
15 // Grouping the test inputs into DoublePoints makes the test cases easier to read.
16 struct DoublePoint {
17 double x;
18 double y;
19 };
20
nearly_equal(double expected,double actual)21 static bool nearly_equal(double expected, double actual) {
22 if (sk_double_nearly_zero(expected)) {
23 return sk_double_nearly_zero(actual);
24 }
25 return sk_doubles_nearly_equal_ulps(expected, actual, 64);
26 }
27
testCubicEvalAtT(skiatest::Reporter * reporter,std::string name,SkSpan<const DoublePoint> curveInputs,double t,const DoublePoint & expectedXY)28 static void testCubicEvalAtT(skiatest::Reporter* reporter, std::string name,
29 SkSpan<const DoublePoint> curveInputs, double t,
30 const DoublePoint& expectedXY) {
31 skiatest::ReporterContext subtest(reporter, name);
32 REPORTER_ASSERT(reporter, curveInputs.size() == 4,
33 "Invalid test case. Should have 4 input points.");
34 REPORTER_ASSERT(reporter, t >= 0.0 && t <= 1.0,
35 "Invalid test case. t %f should be in [0, 1]", t);
36
37 auto [x, y] = SkBezierCubic::EvalAt(reinterpret_cast<const double*>(curveInputs.data()), t);
38
39 REPORTER_ASSERT(reporter, nearly_equal(expectedXY.x, x),
40 "X wrong %1.16f != %1.16f", expectedXY.x, x);
41 REPORTER_ASSERT(reporter, nearly_equal(expectedXY.y, y),
42 "Y wrong %1.16f != %1.16f", expectedXY.y, y);
43 }
44
DEF_TEST(BezierCubicEvalAt,reporter)45 DEF_TEST(BezierCubicEvalAt, reporter) {
46 testCubicEvalAtT(reporter, "linear curve @0.1234",
47 {{ 0, 0 }, { 0, 0 }, { 10, 10 }, { 10, 10 }},
48 0.1234,
49 { 0.4192451819200000, 0.4192451819200000 });
50
51 testCubicEvalAtT(reporter, "linear curve @0.2345",
52 {{ 0, 0 }, { 5, 5 }, { 5, 5 }, { 10, 10 }},
53 0.2345,
54 { 2.8215983862500000, 2.8215983862500000 });
55
56 testCubicEvalAtT(reporter, "Arbitrary Cubic, t=0.0",
57 {{ -10, -20 }, { -7, 5 }, { 14, -2 }, { 3, 13 }},
58 0.0,
59 { -10, -20 });
60
61 testCubicEvalAtT(reporter, "Arbitrary Cubic, t=0.3456",
62 {{ -10, -20 }, { -7, 5 }, { 14, -2 }, { 3, 13 }},
63 0.3456,
64 { -2.503786700800000, -3.31715344793600 });
65
66 testCubicEvalAtT(reporter, "Arbitrary Cubic, t=0.5",
67 {{ -10, -20 }, { -7, 5 }, { 14, -2 }, { 3, 13 }},
68 0.5,
69 { 1.75, 0.25 });
70
71 testCubicEvalAtT(reporter, "Arbitrary Cubic, t=0.7891",
72 {{ -10, -20 }, { -7, 5 }, { 14, -2 }, { 3, 13 }},
73 0.7891,
74 { 6.158763291450000, 5.938550084434000 });
75
76 testCubicEvalAtT(reporter, "Arbitrary Cubic, t=1.0",
77 {{ -10, -20 }, { -7, 5 }, { 14, -2 }, { 3, 13 }},
78 1.0,
79 { 3, 13 });
80 }
81
testCubicConvertToPolynomial(skiatest::Reporter * reporter,std::string name,SkSpan<const DoublePoint> curveInputs,bool yValues,double expectedA,double expectedB,double expectedC,double expectedD)82 static void testCubicConvertToPolynomial(skiatest::Reporter* reporter, std::string name,
83 SkSpan<const DoublePoint> curveInputs, bool yValues,
84 double expectedA, double expectedB,
85 double expectedC, double expectedD) {
86 skiatest::ReporterContext subtest(reporter, name);
87 REPORTER_ASSERT(reporter, curveInputs.size() == 4,
88 "Invalid test case. Need 4 points (start, control, control, end)");
89
90 skiatest::ReporterContext subsubtest(reporter, "SkBezierCurve Implementation");
91 const double* input = &curveInputs[0].x;
92 auto [A, B, C, D] = SkBezierCubic::ConvertToPolynomial(input, yValues);
93
94 REPORTER_ASSERT(reporter, nearly_equal(expectedA, A), "%f != %f", expectedA, A);
95 REPORTER_ASSERT(reporter, nearly_equal(expectedB, B), "%f != %f", expectedB, B);
96 REPORTER_ASSERT(reporter, nearly_equal(expectedC, C), "%f != %f", expectedC, C);
97 REPORTER_ASSERT(reporter, nearly_equal(expectedD, D), "%f != %f", expectedD, D);
98 }
99
DEF_TEST(BezierCubicToPolynomials,reporter)100 DEF_TEST(BezierCubicToPolynomials, reporter) {
101 // See also tests/PathOpsDCubicTest.cpp->SkDCubicPolynomialCoefficients
102 testCubicConvertToPolynomial(reporter, "Arbitrary control points X direction",
103 {{1, 2}, {-3, 4}, {5, -6}, {7, 8}}, false, /*=yValues*/
104 -18, 36, -12, 1
105 );
106 testCubicConvertToPolynomial(reporter, "Arbitrary control points Y direction",
107 {{1, 2}, {-3, 4}, {5, -6}, {7, 8}}, true, /*=yValues*/
108 36, -36, 6, 2
109 );
110 }
111