-
Notifications
You must be signed in to change notification settings - Fork 67
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add an interface to the Path to support constructing Path using SVG #284
Changes from 8 commits
54da4df
a8cbfc4
ff575e9
ba3b2d8
cc9699a
a9435a7
ca47f3c
e21b576
a04c9d4
b5d20d6
310283d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -162,6 +162,28 @@ class Path { | |
*/ | ||
void cubicTo(const Point& control1, const Point& control2, const Point& point); | ||
|
||
/** | ||
* Appends arc to Path. Arc is implemented by one or more conics weighted to describe part oval | ||
* with radii (rx, ry) rotated by xAxisRotate degrees. Arc curves from last Path Point to (x, | ||
* y), choosing one of four possible routes: clockwise or counterclockwise, and smaller or | ||
* larger. | ||
* Arc sweep is always less than 360 degrees. arcTo() appends line to (x, y) if either radii are | ||
* zero, or if last Path Point equals (x, y). arcTo() scales radii (rx, ry) to fit last Path | ||
* Point and (x, y) if both are greater than zero but too small. | ||
* arcTo() appends up to four conic curves. | ||
* arcTo() implements the functionality of SVG arc, although SVG sweep-flag value is opposite the | ||
* integer value of sweep; SVG sweep-flag uses 1 for clockwise,while counter-clockwise direction | ||
* cast to int is zero. | ||
* | ||
* @param rx x radii on axes before x-axis rotation | ||
* @param ry y radii on axes before x-axis rotation | ||
* @param xAxisRotate x-axis rotation in degrees; positive values are clockwise | ||
* @param largeArc chooses smaller or larger arc | ||
* @param reversed Choose the rotation clockwise direction.(clockwise = false) | ||
* @param endPt end of arc | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 这里endPt和参数命名不一致 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 已修改 |
||
*/ | ||
void arcTo(float rx, float ry, float xAxisRotate, ArcSize largeArc, bool reversed, | ||
Point endPoint); | ||
/** | ||
* Closes the current contour of Path. A closed contour connects the first and last Point with | ||
* line, forming a continuous loop. | ||
|
@@ -287,6 +309,11 @@ class Path { | |
*/ | ||
int countVerbs() const; | ||
|
||
/** | ||
* Returns last point on Path in lastPoint. Returns false if point array is empty. | ||
*/ | ||
bool getLastPoint(Point* lastPoint) const; | ||
|
||
private: | ||
std::shared_ptr<PathRef> pathRef = nullptr; | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -96,6 +96,20 @@ enum class PathVerb { | |
Close | ||
}; | ||
|
||
/** | ||
* Specify whether the arc is greater than 180 degrees pair or less than 180 degrees pair. | ||
*/ | ||
enum class ArcSize : uint8_t { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 命名加上Path前缀: PathArcSize |
||
/** | ||
* smaller of arc pair | ||
*/ | ||
Small_ArcSize, | ||
/** | ||
* larger of arc pair | ||
*/ | ||
Large_ArcSize, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 把_ArcSize后缀去掉,Skia里这么命名是因为没有使用enum class,怕重名。 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 已修改 |
||
}; | ||
|
||
/** | ||
* Zero to four Point are stored in points, depending on the returned PathVerb | ||
*/ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -215,6 +215,135 @@ void Path::cubicTo(const Point& control1, const Point& control2, const Point& po | |
cubicTo(control1.x, control1.y, control2.x, control2.y, point.x, point.y); | ||
} | ||
|
||
// This converts the SVG arc to conics based on the SVG standard. | ||
// Code source: | ||
// 1. kdelibs/kdecore/svgicons Niko's code | ||
// 2. webkit/chrome SVGPathNormalizer::decomposeArcToCubic() | ||
// See also SVG implementation notes: | ||
// http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter | ||
// Note that arcSweep bool value is flipped from the original implementation. | ||
void Path::arcTo(float rx, float ry, float xAxisRotate, ArcSize largeArc, bool reversed, | ||
Point endPoint) { | ||
std::array<Point, 2> srcPoints; | ||
this->getLastPoint(&srcPoints[0]); | ||
// If rx = 0 or ry = 0 then this arc is treated as a straight line segment (a "lineto") | ||
// joining the endpoints. | ||
// http://www.w3.org/TR/SVG/implnote.html#ArcOutOfRangeParameters | ||
if (FloatNearlyZero(rx) && FloatNearlyZero(ry)) { | ||
return this->lineTo(endPoint); | ||
} | ||
// If the current point and target point for the arc are identical, it should be treated as a | ||
// zero length path. This ensures continuity in animations. | ||
srcPoints[1] = endPoint; | ||
if (srcPoints[0] == srcPoints[1]) { | ||
return this->lineTo(endPoint); | ||
} | ||
rx = std::abs(rx); | ||
ry = std::abs(ry); | ||
Point midPointDistance = (srcPoints[0] - srcPoints[1]) * 0.5f; | ||
|
||
Matrix pointTransform; | ||
pointTransform.setRotate(-xAxisRotate); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 可以合并为 auto pointTransform = Matrix::MakeRotate() |
||
|
||
Point transformedMidPoint; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 声明变量都记得赋初始值 Point::Zero(), 不然很多隐藏bug。 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 已修改 |
||
pointTransform.mapPoints(&transformedMidPoint, &midPointDistance, 1); | ||
float squareRx = rx * rx; | ||
float squareRy = ry * ry; | ||
float squareX = transformedMidPoint.x * transformedMidPoint.x; | ||
float squareY = transformedMidPoint.y * transformedMidPoint.y; | ||
|
||
// Check if the radii are big enough to draw the arc, scale radii if not. | ||
// http://www.w3.org/TR/SVG/implnote.html#ArcCorrectionOutOfRangeRadii | ||
float radiiScale = squareX / squareRx + squareY / squareRy; | ||
if (radiiScale > 1) { | ||
radiiScale = std::sqrt(radiiScale); | ||
rx *= radiiScale; | ||
ry *= radiiScale; | ||
} | ||
|
||
pointTransform.setScale(1.0f / rx, 1.0f / ry); | ||
pointTransform.preRotate(-xAxisRotate); | ||
|
||
std::array<Point, 2> unitPoints; | ||
pointTransform.mapPoints(unitPoints.data(), srcPoints.data(), unitPoints.size()); | ||
Point delta = unitPoints[1] - unitPoints[0]; | ||
|
||
float d = delta.x * delta.x + delta.y * delta.y; | ||
float scaleFactorSquared = std::max(1 / d - 0.25f, 0.f); | ||
|
||
float scaleFactor = std::sqrt(scaleFactorSquared); | ||
if (reversed != static_cast<bool>(largeArc)) { // flipped from the original implementation | ||
scaleFactor = -scaleFactor; | ||
} | ||
delta *= scaleFactor; | ||
Point centerPoint = unitPoints[0] + unitPoints[1]; | ||
centerPoint *= 0.5f; | ||
centerPoint.offset(-delta.y, delta.x); | ||
unitPoints[0] -= centerPoint; | ||
unitPoints[1] -= centerPoint; | ||
float theta1 = std::atan2(unitPoints[0].y, unitPoints[0].x); | ||
float theta2 = std::atan2(unitPoints[1].y, unitPoints[1].x); | ||
float thetaArc = theta2 - theta1; | ||
if (thetaArc < 0 && !reversed) { // arcSweep flipped from the original implementation | ||
thetaArc += M_PI_F * 2.0f; | ||
} else if (thetaArc > 0 && reversed) { // arcSweep flipped from the original implementation | ||
thetaArc -= M_PI_F * 2.0f; | ||
} | ||
|
||
// Very tiny angles cause our subsequent math to go wonky | ||
// but a larger value is probably ok too. | ||
if (std::abs(thetaArc) < (M_PI_F / (1000 * 1000))) { | ||
return this->lineTo(endPoint); | ||
} | ||
|
||
pointTransform.setRotate(xAxisRotate); | ||
pointTransform.preScale(rx, ry); | ||
|
||
// the arc may be slightly bigger than 1/4 circle, so allow up to 1/3rd | ||
float segments = std::ceil(std::abs(thetaArc / (2 * M_PI_F / 3))); | ||
float thetaWidth = thetaArc / segments; | ||
float t = std::tan(0.5f * thetaWidth); | ||
if (!FloatsAreFinite(&t, 1)) { | ||
return; | ||
} | ||
float startTheta = theta1; | ||
float w = std::sqrt(0.5f + std::cos(thetaWidth) * 0.5f); | ||
auto float_is_integer = [](float scalar) -> bool { return scalar == std::floor(scalar); }; | ||
bool expectIntegers = FloatNearlyZero(M_PI_F * 0.5f - std::abs(thetaWidth)) && | ||
float_is_integer(rx) && float_is_integer(ry) && | ||
float_is_integer(endPoint.x) && float_is_integer(endPoint.y); | ||
|
||
auto path = &(writableRef()->path); | ||
for (int i = 0; i < static_cast<int>(segments); ++i) { | ||
float endTheta = startTheta + thetaWidth; | ||
float sinEndTheta = SkScalarSinSnapToZero(endTheta); | ||
float cosEndTheta = SkScalarCosSnapToZero(endTheta); | ||
|
||
unitPoints[1].set(cosEndTheta, sinEndTheta); | ||
unitPoints[1] += centerPoint; | ||
unitPoints[0] = unitPoints[1]; | ||
unitPoints[0].offset(t * sinEndTheta, -t * cosEndTheta); | ||
std::array<Point, 2> mapped; | ||
pointTransform.mapPoints(mapped.data(), unitPoints.data(), unitPoints.size()); | ||
|
||
// Computing the arc width introduces rounding errors that cause arcs to start outside their | ||
// marks.A round rect may lose convexity as a result.If the input values are on integers, | ||
// place the conic on integers as well. | ||
if (expectIntegers) { | ||
for (Point& point : mapped) { | ||
point.x = std::round(point.x); | ||
point.y = std::round(point.y); | ||
} | ||
} | ||
path->conicTo(mapped[0].x, mapped[0].y, mapped[1].x, mapped[1].y, w); | ||
startTheta = endTheta; | ||
} | ||
|
||
// The final point should match the input point (by definition); replace it to | ||
// ensure that rounding errors in the above math don't cause any problems. | ||
path->setLastPt(endPoint.x, endPoint.y); | ||
} | ||
|
||
void Path::close() { | ||
writableRef()->path.close(); | ||
} | ||
|
@@ -415,4 +544,14 @@ int Path::countPoints() const { | |
int Path::countVerbs() const { | ||
return pathRef->path.countVerbs(); | ||
} | ||
|
||
bool Path::getLastPoint(Point* lastPoint) const { | ||
SkPoint skPoint; | ||
if (pathRef->path.getLastPt(&skPoint)) { | ||
lastPoint->set(skPoint.fX, skPoint.fY); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 这里对lastPoint的指针访问是否需要判空?检查一下PathKit里的对应实现是否判空,如果那边判空了,这里也要加上。 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 需要判空,已补充 |
||
return true; | ||
} | ||
return false; | ||
}; | ||
|
||
} // namespace tgfx |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
参数里已经没有sweep flag了
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
这里应该是指的svg里面的参数
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
用Github Copilot重写一下这段注释吧,prompt:Rewrite it to sound more natural for a native speaker.