mirror of
https://github.com/thorvg/thorvg.git
synced 2025-06-08 05:33:36 +00:00
gl_engine: Fix for rendering short paths
Ensure they do not terminate prematurely for paths with a step of 0 or exceptionally small values when a valid stroke-width is present. In my opinion, the optimal approach was to separate vertex generation into dedicated methods: strokeRoundPoint and strokeSquarePoint. My update supports two different stroke-cap styles. I have also tested it with various files (JSON, SVG) as well as a small example application similar to the one included in the previous pull request (#3066). issue: https://github.com/thorvg/thorvg/issues/3065
This commit is contained in:
parent
b041bdab4a
commit
f2883a31da
2 changed files with 78 additions and 13 deletions
|
@ -1655,38 +1655,42 @@ void Stroker::doStroke(const PathCommand *cmds, uint32_t cmd_count, const Point
|
||||||
mResGlPoints->reserve(pts_count * 4 + 16);
|
mResGlPoints->reserve(pts_count * 4 + 16);
|
||||||
mResIndices->reserve(pts_count * 3);
|
mResIndices->reserve(pts_count * 3);
|
||||||
|
|
||||||
|
auto validStrokeCap = false;
|
||||||
|
|
||||||
for (uint32_t i = 0; i < cmd_count; i++) {
|
for (uint32_t i = 0; i < cmd_count; i++) {
|
||||||
switch (cmds[i]) {
|
switch (cmds[i]) {
|
||||||
case PathCommand::MoveTo: {
|
case PathCommand::MoveTo: {
|
||||||
if (mStrokeState.hasMove) {
|
if (validStrokeCap) { // check this, so we can skip if path only contains move instruction
|
||||||
strokeCap();
|
strokeCap();
|
||||||
mStrokeState.hasMove = false;
|
validStrokeCap = false;
|
||||||
}
|
}
|
||||||
mStrokeState.hasMove = true;
|
|
||||||
mStrokeState.firstPt = *pts;
|
mStrokeState.firstPt = *pts;
|
||||||
mStrokeState.firstPtDir = {0.0f, 0.0f};
|
mStrokeState.firstPtDir = {0.0f, 0.0f};
|
||||||
mStrokeState.prevPt = *pts;
|
mStrokeState.prevPt = *pts;
|
||||||
mStrokeState.prevPtDir = {0.0f, 0.0f};
|
mStrokeState.prevPtDir = {0.0f, 0.0f};
|
||||||
pts++;
|
pts++;
|
||||||
|
validStrokeCap = false;
|
||||||
} break;
|
} break;
|
||||||
case PathCommand::LineTo: {
|
case PathCommand::LineTo: {
|
||||||
|
validStrokeCap = true;
|
||||||
this->strokeLineTo(*pts);
|
this->strokeLineTo(*pts);
|
||||||
pts++;
|
pts++;
|
||||||
} break;
|
} break;
|
||||||
case PathCommand::CubicTo: {
|
case PathCommand::CubicTo: {
|
||||||
|
validStrokeCap = true;
|
||||||
this->strokeCubicTo(pts[0], pts[1], pts[2]);
|
this->strokeCubicTo(pts[0], pts[1], pts[2]);
|
||||||
pts += 3;
|
pts += 3;
|
||||||
} break;
|
} break;
|
||||||
case PathCommand::Close: {
|
case PathCommand::Close: {
|
||||||
this->strokeClose();
|
this->strokeClose();
|
||||||
|
|
||||||
mStrokeState.hasMove = false;
|
validStrokeCap = false;
|
||||||
} break;
|
} break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
strokeCap();
|
if (validStrokeCap) strokeCap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1707,17 +1711,22 @@ void Stroker::doDashStroke(const PathCommand *cmds, uint32_t cmd_count, const Po
|
||||||
|
|
||||||
void Stroker::strokeCap()
|
void Stroker::strokeCap()
|
||||||
{
|
{
|
||||||
if (mStrokeState.firstPt == mStrokeState.prevPt) return;
|
|
||||||
if (mStrokeCap == StrokeCap::Butt) return;
|
if (mStrokeCap == StrokeCap::Butt) return;
|
||||||
|
|
||||||
if (mStrokeCap == StrokeCap::Square) {
|
if (mStrokeCap == StrokeCap::Square) {
|
||||||
|
if (mStrokeState.firstPt == mStrokeState.prevPt) strokeSquarePoint(mStrokeState.firstPt);
|
||||||
|
else {
|
||||||
strokeSquare(mStrokeState.firstPt, {-mStrokeState.firstPtDir.x, -mStrokeState.firstPtDir.y});
|
strokeSquare(mStrokeState.firstPt, {-mStrokeState.firstPtDir.x, -mStrokeState.firstPtDir.y});
|
||||||
strokeSquare(mStrokeState.prevPt, mStrokeState.prevPtDir);
|
strokeSquare(mStrokeState.prevPt, mStrokeState.prevPtDir);
|
||||||
|
}
|
||||||
} else if (mStrokeCap == StrokeCap::Round) {
|
} else if (mStrokeCap == StrokeCap::Round) {
|
||||||
|
if (mStrokeState.firstPt == mStrokeState.prevPt) strokeRoundPoint(mStrokeState.firstPt);
|
||||||
|
else {
|
||||||
strokeRound(mStrokeState.firstPt, {-mStrokeState.firstPtDir.x, -mStrokeState.firstPtDir.y});
|
strokeRound(mStrokeState.firstPt, {-mStrokeState.firstPtDir.x, -mStrokeState.firstPtDir.y});
|
||||||
strokeRound(mStrokeState.prevPt, mStrokeState.prevPtDir);
|
strokeRound(mStrokeState.prevPt, mStrokeState.prevPtDir);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void Stroker::strokeLineTo(const Point& curr)
|
void Stroker::strokeLineTo(const Point& curr)
|
||||||
|
@ -1807,8 +1816,6 @@ void Stroker::strokeClose()
|
||||||
|
|
||||||
// join firstPt with prevPt
|
// join firstPt with prevPt
|
||||||
this->strokeJoin(mStrokeState.firstPtDir);
|
this->strokeJoin(mStrokeState.firstPtDir);
|
||||||
|
|
||||||
mStrokeState.hasMove = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1887,6 +1894,33 @@ void Stroker::strokeRound(const Point &prev, const Point& curr, const Point& cen
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Stroker::strokeRoundPoint(const Point &p)
|
||||||
|
{
|
||||||
|
// Fixme: just use bezier curve to calculate step count
|
||||||
|
auto count = _bezierCurveCount(_bezFromArc(p, p, strokeRadius())) * 2;
|
||||||
|
auto c = _pushVertex(mResGlPoints, p.x, p.y);
|
||||||
|
auto step = 2 * M_PI / (count - 1);
|
||||||
|
|
||||||
|
for (uint32_t i = 1; i <= static_cast<uint32_t>(count); i++) {
|
||||||
|
float angle = i * step;
|
||||||
|
Point dir = {cos(angle), sin(angle)};
|
||||||
|
Point out = p + dir * strokeRadius();
|
||||||
|
auto oi = _pushVertex(mResGlPoints, out.x, out.y);
|
||||||
|
|
||||||
|
if (oi > 1) {
|
||||||
|
mResIndices->push(c);
|
||||||
|
mResIndices->push(oi);
|
||||||
|
mResIndices->push(oi - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mLeftTop.x = std::min(mLeftTop.x, p.x - strokeRadius());
|
||||||
|
mLeftTop.y = std::min(mLeftTop.y, p.y - strokeRadius());
|
||||||
|
mRightBottom.x = std::max(mRightBottom.x, p.x + strokeRadius());
|
||||||
|
mRightBottom.y = std::max(mRightBottom.y, p.y + strokeRadius());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void Stroker::strokeMiter(const Point& prev, const Point& curr, const Point& center)
|
void Stroker::strokeMiter(const Point& prev, const Point& curr, const Point& center)
|
||||||
{
|
{
|
||||||
auto pp1 = prev - center;
|
auto pp1 = prev - center;
|
||||||
|
@ -1963,6 +1997,36 @@ void Stroker::strokeSquare(const Point& p, const Point& outDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Stroker::strokeSquarePoint(const Point& p)
|
||||||
|
{
|
||||||
|
auto offsetX = Point{strokeRadius(), 0.0f};
|
||||||
|
auto offsetY = Point{0.0f, strokeRadius()};
|
||||||
|
|
||||||
|
auto a = p + offsetX + offsetY;
|
||||||
|
auto b = p - offsetX + offsetY;
|
||||||
|
auto c = p - offsetX - offsetY;
|
||||||
|
auto d = p + offsetX - offsetY;
|
||||||
|
|
||||||
|
auto ai = _pushVertex(mResGlPoints, a.x, a.y);
|
||||||
|
auto bi = _pushVertex(mResGlPoints, b.x, b.y);
|
||||||
|
auto ci = _pushVertex(mResGlPoints, c.x, c.y);
|
||||||
|
auto di = _pushVertex(mResGlPoints, d.x, d.y);
|
||||||
|
|
||||||
|
mResIndices->push(ai);
|
||||||
|
mResIndices->push(bi);
|
||||||
|
mResIndices->push(ci);
|
||||||
|
|
||||||
|
mResIndices->push(ci);
|
||||||
|
mResIndices->push(di);
|
||||||
|
mResIndices->push(ai);
|
||||||
|
|
||||||
|
mLeftTop.x = std::min(mLeftTop.x, std::min(std::min(a.x, b.x), std::min(c.x, d.x)));
|
||||||
|
mLeftTop.y = std::min(mLeftTop.y, std::min(std::min(a.y, b.y), std::min(c.y, d.y)));
|
||||||
|
mRightBottom.x = std::max(mRightBottom.x, std::max(std::max(a.x, b.x), std::max(c.x, d.x)));
|
||||||
|
mRightBottom.y = std::max(mRightBottom.y, std::max(std::max(a.y, b.y), std::max(c.y, d.y)));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void Stroker::strokeRound(const Point& p, const Point& outDir)
|
void Stroker::strokeRound(const Point& p, const Point& outDir)
|
||||||
{
|
{
|
||||||
auto normal = Point{-outDir.y, outDir.x};
|
auto normal = Point{-outDir.y, outDir.x};
|
||||||
|
|
|
@ -79,7 +79,6 @@ class Stroker final
|
||||||
Point firstPtDir;
|
Point firstPtDir;
|
||||||
Point prevPt;
|
Point prevPt;
|
||||||
Point prevPtDir;
|
Point prevPtDir;
|
||||||
bool hasMove = false;
|
|
||||||
};
|
};
|
||||||
public:
|
public:
|
||||||
Stroker(Array<float>* points, Array<uint32_t>* indices, const Matrix& matrix);
|
Stroker(Array<float>* points, Array<uint32_t>* indices, const Matrix& matrix);
|
||||||
|
@ -106,7 +105,9 @@ private:
|
||||||
void strokeMiter(const Point& prev, const Point& curr, const Point& center);
|
void strokeMiter(const Point& prev, const Point& curr, const Point& center);
|
||||||
void strokeBevel(const Point& prev, const Point& curr, const Point& center);
|
void strokeBevel(const Point& prev, const Point& curr, const Point& center);
|
||||||
void strokeSquare(const Point& p, const Point& outDir);
|
void strokeSquare(const Point& p, const Point& outDir);
|
||||||
|
void strokeSquarePoint(const Point& p);
|
||||||
void strokeRound(const Point& p, const Point& outDir);
|
void strokeRound(const Point& p, const Point& outDir);
|
||||||
|
void strokeRoundPoint(const Point& p);
|
||||||
|
|
||||||
Array<float>* mResGlPoints;
|
Array<float>* mResGlPoints;
|
||||||
Array<uint32_t>* mResIndices;
|
Array<uint32_t>* mResIndices;
|
||||||
|
|
Loading…
Add table
Reference in a new issue