1 /*
2 * Copyright (c) 2025 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "utils_path.h"
17
18 #include <map>
19
20 #include "include/core/SkPoint.h"
21 #include "src/core/SkGeometry.h"
22 #include "src/core/SkReadBuffer.h"
23 #include "src/core/SkWriteBuffer.h"
24
25 #include "draw/path.h"
26 #include "utils/log.h"
27 #include "utils/point.h"
28
29 namespace OHOS {
30 namespace Rosen {
31 namespace Drawing {
32 static constexpr float QUADRATIC_BINOMIAL_COEFF = 2.0f;
33 static constexpr int CONIC_CTRL_POINT_COUNT = 4;
34 static constexpr int CUBIC_BEZIER_BINOMIAL = 3;
35 static constexpr int MAX_BEZIER_SEGMENTS = 5000000; // 5000000 is a safe upper bound.
36 static constexpr int MID_T_DIVISOR = 2;
37 static constexpr int QUADRATIC_BEZIER_POINT_COUNT = 3;
38 static constexpr int QUADRATIC_STRIDE = 2;
39
ComputeQuadratic(float param,float start,float control,float end)40 float ComputeQuadratic(float param, float start, float control, float end)
41 {
42 float invParam = 1 - param;
43 float invSqr = invParam * invParam;
44 float crossTerm = QUADRATIC_BINOMIAL_COEFF * invParam * param;
45 float paramSqr = param * param;
46 return invSqr * start + crossTerm * control + paramSqr * end;
47 }
48
ComputeCubic(float param,float start,float control1,float control2,float end)49 static float ComputeCubic(float param, float start, float control1, float control2, float end)
50 {
51 float invParam = 1 - param;
52 float invSqr = invParam * invParam;
53 float invCubed = invSqr * invParam;
54 float paramSqr = param * param;
55 float paramCubed = paramSqr * param;
56 return invCubed * start + CUBIC_BEZIER_BINOMIAL * invSqr * param * control1 +
57 CUBIC_BEZIER_BINOMIAL * invParam * paramSqr * control2 + paramCubed * end;
58 }
59
ShouldSubdivide(const Point * controlPoints,UtilsPath::BezierFunction calculationBezier,float startT,const Point & startPt,float endT,const Point & endPt,float & midParam,Point & midPt,float maxError)60 static bool ShouldSubdivide(const Point* controlPoints, UtilsPath::BezierFunction calculationBezier, float startT,
61 const Point& startPt, float endT, const Point& endPt, float& midParam, Point& midPt, float maxError)
62 {
63 midParam = (startT + endT) / MID_T_DIVISOR;
64 midPt = calculationBezier(midParam, controlPoints);
65 float avgX = (startPt.GetX() + endPt.GetX()) / MID_T_DIVISOR;
66 float avgY = (startPt.GetY() + endPt.GetY()) / MID_T_DIVISOR;
67 float dx = midPt.GetX() - avgX;
68 float dy = midPt.GetY() - avgY;
69 float error = dx * dx + dy * dy;
70 return error > maxError;
71 }
72
Distance(const Point & p1,const Point & p2)73 scalar UtilsPath::Distance(const Point& p1, const Point& p2)
74 {
75 return SkPoint::Distance(reinterpret_cast<const SkPoint &>(p1), reinterpret_cast<const SkPoint &>(p2));
76 }
77
AddMove(const Point & point,std::vector<Point> & approxPoints,std::vector<float> & approxLengths)78 void UtilsPath::AddMove(const Point& point, std::vector<Point>& approxPoints, std::vector<float>& approxLengths)
79 {
80 float length = approxLengths.empty() ? 0.0f : approxLengths.back();
81 approxPoints.push_back(point);
82 approxLengths.push_back(length);
83 }
84
AddLine(const Point & point,std::vector<Point> & approxPoints,std::vector<float> & approxLengths)85 void UtilsPath::AddLine(const Point& point, std::vector<Point>& approxPoints, std::vector<float>& approxLengths)
86 {
87 if (approxPoints.empty()) {
88 approxPoints.push_back(Point(0, 0));
89 approxLengths.push_back(0);
90 } else if (approxPoints.back() == point) {
91 return;
92 }
93 float length = approxLengths.back() + Distance(approxPoints.back(), point);
94 approxPoints.push_back(point);
95 approxLengths.push_back(length);
96 }
97
CalculateQuadraticBezier(float t,const Point * points)98 Point UtilsPath::CalculateQuadraticBezier(float t, const Point* points)
99 {
100 // 0, 1, 2 are the control points of a quad bezier curve.
101 float x = ComputeQuadratic(t, points[0].GetX(), points[1].GetX(), points[2].GetX());
102 float y = ComputeQuadratic(t, points[0].GetY(), points[1].GetY(), points[2].GetY());
103 return Point(x, y);
104 }
105
CalculateCubicBezier(float t,const Point * points)106 Point UtilsPath::CalculateCubicBezier(float t, const Point* points)
107 {
108 // 0, 1, 2, 3 are the control points of a cubic bezier curve.
109 float x = ComputeCubic(t, points[0].GetX(), points[1].GetX(), points[2].GetX(), points[3].GetX());
110 float y = ComputeCubic(t, points[0].GetY(), points[1].GetY(), points[2].GetY(), points[3].GetY());
111 return Point(x, y);
112 }
113
AddBezier(const Point * points,BezierFunction calculationBezier,std::vector<Point> & approxPoints,std::vector<float> & approxLengths,float errorSquared,bool doubleCheck)114 void UtilsPath::AddBezier(const Point* points, BezierFunction calculationBezier, std::vector<Point>& approxPoints,
115 std::vector<float>& approxLengths, float errorSquared, bool doubleCheck)
116 {
117 using TimePointMap = std::map<float, Point>;
118 TimePointMap timeToPoints;
119 timeToPoints[0] = calculationBezier(0.0f, points);
120 timeToPoints[1] = calculationBezier(1.0f, points);
121 auto it = timeToPoints.begin();
122 auto nextIt = std::next(it);
123 size_t count = 0;
124 while (nextIt != timeToPoints.end()) {
125 count++;
126 if (count > MAX_BEZIER_SEGMENTS) {
127 LOGW("UtilsPath::AddBezier count too large.");
128 break;
129 }
130 float midT;
131 Point midPt;
132 bool split = ShouldSubdivide(points, calculationBezier, it->first, it->second, nextIt->first, nextIt->second,
133 midT, midPt, errorSquared);
134 if (!split && doubleCheck) {
135 float qT;
136 Point qPt;
137 split = ShouldSubdivide(points, calculationBezier, it->first, it->second, midT, midPt, qT, qPt,
138 errorSquared);
139 if (split) {
140 doubleCheck = false;
141 }
142 }
143 if (split) {
144 nextIt = timeToPoints.insert(it, { midT, midPt });
145 } else {
146 ++it;
147 nextIt = std::next(it);
148 }
149 }
150 for (const auto& [_, pt] : timeToPoints) {
151 AddLine(pt, approxPoints, approxLengths);
152 }
153 }
154
AddConic(PathIter & pathIter,const Point * points,std::vector<Point> & approxPoints,std::vector<float> & approxLengths,float errorConic)155 void UtilsPath::AddConic(PathIter& pathIter, const Point* points, std::vector<Point>& approxPoints,
156 std::vector<float>& approxLengths, float errorConic)
157 {
158 SkAutoConicToQuads converter;
159 SkPoint skPoints[CONIC_CTRL_POINT_COUNT];
160 for (int i = 0; i < CONIC_CTRL_POINT_COUNT; ++i) {
161 skPoints[i] = SkPoint::Make(points[i].GetX(), points[i].GetY());
162 }
163 const SkPoint* quads = converter.computeQuads(skPoints, pathIter.ConicWeight(), errorConic);
164 if (quads == nullptr) {
165 LOGE("UtilsPath::AddConic quads is nullptr.");
166 return;
167 }
168 for (int i = 0; i < converter.countQuads(); ++i) {
169 Point quadPoints[QUADRATIC_BEZIER_POINT_COUNT];
170 for (int j = 0; j < QUADRATIC_BEZIER_POINT_COUNT; ++j) {
171 quadPoints[j] = Point(quads[i * QUADRATIC_STRIDE + j].x(), quads[i * QUADRATIC_STRIDE + j].y());
172 }
173 UtilsPath::AddBezier(quadPoints, UtilsPath::CalculateQuadraticBezier, approxPoints, approxLengths, errorConic,
174 false);
175 }
176 }
177
178 } // namespace Drawing
179 } // namespace Rosen
180 } // namespace OHOS
181