gl_engine: Fix calculation error in path triming

Basically rewrite the PathTrim code, correct the Line and Bezier split
function calling.
Also the trim situation where start is greater than end can be handled correctly.
This commit is contained in:
RuiwenTang 2024-12-05 10:24:09 +08:00 committed by Mira Grudzinska
parent babff908cd
commit be538bacf7
2 changed files with 159 additions and 186 deletions

View file

@ -1666,72 +1666,15 @@ void Stroker::stroke(const RenderShape *rshape)
auto end = 0.0f; auto end = 0.0f;
rshape->stroke->strokeTrim(begin, end); rshape->stroke->strokeTrim(begin, end);
if (rshape->stroke->trim.simultaneous) { if (begin == end) return;
auto startCmds = cmds;
auto currCmds = cmds;
int ptsNum = 0;
for (uint32_t i = 0; i < cmdCnt; i++) {
switch (*currCmds) {
case PathCommand::MoveTo: {
if (currCmds != startCmds) {
PathTrim trim{};
if (trim.trim(startCmds, currCmds - startCmds, pts, ptsNum, begin, end)) {
const auto& sCmds = trim.cmds();
const auto& sPts = trim.pts();
doStroke(sCmds.data, sCmds.count, sPts.data, sPts.count);
}
startCmds = currCmds;
pts += ptsNum;
ptsNum = 0;
}
currCmds++;
ptsNum++;
break;
}
case PathCommand::LineTo:
currCmds++;
ptsNum++;
break;
case PathCommand::CubicTo:
currCmds++;
ptsNum += 3;
break;
case PathCommand::Close: {
PathTrim trim{};
currCmds++;
if (trim.trim(startCmds, currCmds - startCmds, pts, ptsNum, begin, end)) {
const auto& sCmds = trim.cmds();
const auto& sPts = trim.pts();
doStroke(sCmds.data, sCmds.count, sPts.data, sPts.count);
}
startCmds = currCmds;
pts += ptsNum;
ptsNum = 0;
break;
}
}
}
if (startCmds != currCmds && ptsNum > 0) { if (begin > end) {
PathTrim trim{}; doTrimStroke(cmds, cmdCnt, pts, ptsCnt, rshape->stroke->trim.simultaneous, begin, 1.0f);
doTrimStroke(cmds, cmdCnt, pts, ptsCnt, rshape->stroke->trim.simultaneous, 0.0f, end);
if (trim.trim(startCmds, currCmds - startCmds, pts, ptsNum, begin, end)) {
const auto& sCmds = trim.cmds();
const auto& sPts = trim.pts();
doStroke(sCmds.data, sCmds.count, sPts.data, sPts.count);
}
startCmds = currCmds;
pts += ptsNum;
ptsNum = 0;
}
} else { } else {
PathTrim trim{}; doTrimStroke(cmds, cmdCnt, pts, ptsCnt, rshape->stroke->trim.simultaneous, begin, end);
if (trim.trim(cmds, cmdCnt, pts, ptsCnt, begin, end)) {
const auto& sCmds = trim.cmds();
const auto& sPts = trim.pts();
doStroke(sCmds.data, sCmds.count, sPts.data, sPts.count);
}
} }
return; return;
} }
@ -1742,6 +1685,7 @@ void Stroker::stroke(const RenderShape *rshape)
else doDashStroke(cmds, cmdCnt, pts, ptsCnt, dashCnt, dash_pattern); else doDashStroke(cmds, cmdCnt, pts, ptsCnt, dashCnt, dash_pattern);
} }
RenderRegion Stroker::bounds() const RenderRegion Stroker::bounds() const
{ {
return RenderRegion { return RenderRegion {
@ -1752,6 +1696,76 @@ RenderRegion Stroker::bounds() const
}; };
} }
void Stroker::doTrimStroke(const PathCommand* cmds, uint32_t cmdCnt, const Point* pts, uint32_t ptsCnt, bool simultaneous, float start, float end)
{
if (simultaneous) {
auto startCmds = cmds;
auto currCmds = cmds;
int ptsNum = 0;
for (uint32_t i = 0; i < cmdCnt; i++) {
switch (*currCmds) {
case PathCommand::MoveTo: {
if (currCmds != startCmds) {
PathTrim trim{};
if (trim.trim(startCmds, currCmds - startCmds, pts, ptsNum, start, end)) {
const auto& sCmds = trim.cmds();
const auto& sPts = trim.pts();
doStroke(sCmds.data, sCmds.count, sPts.data, sPts.count);
}
startCmds = currCmds;
pts += ptsNum;
ptsNum = 0;
}
currCmds++;
ptsNum++;
break;
}
case PathCommand::LineTo:
currCmds++;
ptsNum++;
break;
case PathCommand::CubicTo:
currCmds++;
ptsNum += 3;
break;
case PathCommand::Close: {
PathTrim trim{};
currCmds++;
if (trim.trim(startCmds, currCmds - startCmds, pts, ptsNum, start, end)) {
const auto& sCmds = trim.cmds();
const auto& sPts = trim.pts();
doStroke(sCmds.data, sCmds.count, sPts.data, sPts.count);
}
startCmds = currCmds;
pts += ptsNum;
ptsNum = 0;
break;
}
}
}
if (startCmds != currCmds && ptsNum > 0) {
PathTrim trim{};
if (trim.trim(startCmds, currCmds - startCmds, pts, ptsNum, start, end)) {
const auto& sCmds = trim.cmds();
const auto& sPts = trim.pts();
doStroke(sCmds.data, sCmds.count, sPts.data, sPts.count);
}
startCmds = currCmds;
pts += ptsNum;
ptsNum = 0;
}
} else {
PathTrim trim{};
if (trim.trim(cmds, cmdCnt, pts, ptsCnt, start, end)) {
const auto& sCmds = trim.cmds();
const auto& sPts = trim.pts();
doStroke(sCmds.data, sCmds.count, sPts.data, sPts.count);
}
}
}
void Stroker::doStroke(const PathCommand *cmds, uint32_t cmd_count, const Point *pts, uint32_t pts_count) void Stroker::doStroke(const PathCommand *cmds, uint32_t cmd_count, const Point *pts, uint32_t pts_count)
{ {
mResGlPoints->reserve(pts_count * 4 + 16); mResGlPoints->reserve(pts_count * 4 + 16);
@ -2361,159 +2375,117 @@ void PathTrim::trimPath(const PathCommand* cmds, uint32_t cmd_count, const Point
{ {
auto pos = 0.0f; auto pos = 0.0f;
Point zero = {0.0f, 0.0f}; Point zero = {0.0f, 0.0f};
const Point* prev = nullptr; Point prev = {};
const Point* begin = nullptr; Point begin = {};
auto closed = true; auto closed = true;
auto pushedMoveTo = false; auto pushedMoveTo = false;
auto hasLineTo = false; auto hasLineTo = false;
auto handle_line_to = [&](const Point* p1, const Point* p2) { auto handle_line_to = [&](const Point* p1, const Point* p2) {
auto currLen = length(p1, p2); auto currLen = length(p1, p2);
if (pos + currLen < start) { if (pos + currLen <= start) {
pos += currLen; pos += currLen;
prev = *p2;
return; return;
} }
if (pos >= start && pos + currLen <= end) {
// the entire edge is within the trim range
if (!pushedMoveTo) {
mCmds.push(PathCommand::MoveTo);
mPts.push(*p1);
pushedMoveTo = true;
}
mCmds.push(PathCommand::LineTo);
mPts.push(*p2);
} else if (pos >= start) {
// split the edge and save the left part
Line l{ *p1, *p2 };
Line left, right;
l.split((pos - end) / currLen, left, right);
if (!pushedMoveTo) {
mCmds.push(PathCommand::MoveTo);
mPts.push(*p1);
pushedMoveTo = true;
}
mCmds.push(PathCommand::LineTo);
mPts.push(left.pt2);
} else if (pos + currLen <= end) {
// split the edge and save the right part
Line l{ *p1, *p2 };
Line left, right;
l.split((pos + currLen - start) / currLen, left, right);
if (!pushedMoveTo) {
mCmds.push(PathCommand::MoveTo);
mPts.push(right.pt1);
pushedMoveTo = true;
}
mCmds.push(PathCommand::LineTo);
mPts.push(right.pt2);
} else {
// only part of the edge is within the trim range
Line l{ *p1, *p2 };
Line left, right;
// find the start point
l.split((start - pos) / currLen, left, right);
auto startP = left.pt2;
// find the end point if (pos >= end) {
l.split((end - pos) / currLen, left, right); prev = *p2;
auto endP = right.pt1; return;
if (!pushedMoveTo) {
mCmds.push(PathCommand::MoveTo);
mPts.push(startP);
pushedMoveTo = true;
}
mCmds.push(PathCommand::LineTo);
mPts.push(endP);
} }
pos += currLen;
Line line{*p1, *p2};
if (pos < start) {
Line left, right;
line.split(start - pos, left, right);
pos += left.length();
line = right;
}
if (pos + currLen > end) {
Line left, right;
line.split(end - pos, left, right);
pos += left.length();
line = left;
}
if (!pushedMoveTo) {
mCmds.push(PathCommand::MoveTo);
mPts.push(line.pt1);
pushedMoveTo = true;
begin = line.pt1;
}
pos += line.length();
mCmds.push(PathCommand::LineTo);
mPts.push(line.pt2);
prev = line.pt2;
hasLineTo = true;
closed = false;
}; };
for (uint32_t i = 0; i < cmd_count; i++) { for (uint32_t i = 0; i < cmd_count; i++) {
if (pos - end > 0.001f) return; // we are done if (pos - end > 0.001f) return; // we are done
if (pos >= end) return;
switch (cmds[i]) { switch (cmds[i]) {
case PathCommand::MoveTo: { case PathCommand::MoveTo: {
prev = pts; prev = *pts;
begin = pts; begin = *pts;
pts++; pts++;
closed = false; closed = false;
break; break;
} }
case PathCommand::LineTo: { case PathCommand::LineTo: {
if (prev == nullptr) prev = begin = &zero; handle_line_to(&prev, pts);
handle_line_to(prev, pts);
hasLineTo = true; hasLineTo = true;
prev = pts;
pts++; pts++;
break; break;
} }
case PathCommand::CubicTo: { case PathCommand::CubicTo: {
if (prev == nullptr) prev = begin = &zero; Bezier b{ prev, pts[0], pts[1], pts[2]};
Bezier b{ *prev, pts[0], pts[1], pts[2]};
auto currLen = b.length();
if (pos + currLen < start || currLen < 0.001) { auto currLen = b.length();
if (pos + currLen <= start) {
pos += currLen; pos += currLen;
prev = pts + 2; prev = pts[2];
pts += 3; pts += 3;
break; break;
} }
if (pos >= start && pos + currLen <= end) {
// the entire edge is within the trim range if (pos < start) {
if (!pushedMoveTo) {
mCmds.push(PathCommand::MoveTo);
mPts.push(*prev);
pushedMoveTo = true;
}
mCmds.push(PathCommand::CubicTo);
mPts.push(pts[0]);
mPts.push(pts[1]);
mPts.push(pts[2]);
} else if (pos >= start) {
// split the edge and save the left part
Bezier left;
b.split((end - pos) / currLen, left);
if (!pushedMoveTo) {
mCmds.push(PathCommand::MoveTo);
mPts.push(*prev);
pushedMoveTo = true;
}
mCmds.push(PathCommand::CubicTo);
mPts.push(left.ctrl1);
mPts.push(left.ctrl2);
mPts.push(left.end);
} else if (pos + currLen <= end) {
// split the edge and save the right part
Bezier left, right; Bezier left, right;
b.split((start - pos) / currLen, left, right); b.split(start - pos, left, right);
if (!pushedMoveTo) { pos += left.length();
mCmds.push(PathCommand::MoveTo); b = right;
mPts.push(right.start);
pushedMoveTo = true;
}
mCmds.push(PathCommand::CubicTo);
mPts.push(right.ctrl1);
mPts.push(right.ctrl2);
mPts.push(right.end);
} else {
// only part of the edge is within the trim range
Bezier left, right;
b.split((start - pos) / currLen, left, right);
right.split((end - start) / right.length(), left);
if (!pushedMoveTo) {
mCmds.push(PathCommand::MoveTo);
mPts.push(left.start);
pushedMoveTo = true;
}
mCmds.push(PathCommand::CubicTo);
mPts.push(left.ctrl1);
mPts.push(left.ctrl2);
mPts.push(left.end);
} }
if (pos + currLen > end) {
Bezier left, right;
b.split(end - pos, left, right);
pos += left.length();
b = left;
}
if (!pushedMoveTo) {
mCmds.push(PathCommand::MoveTo);
mPts.push(b.start);
pushedMoveTo = true;
}
pos += b.length();
mCmds.push(PathCommand::CubicTo);
mPts.push(b.ctrl1);
mPts.push(b.ctrl2);
mPts.push(b.end);
prev = b.end;
hasLineTo = true; hasLineTo = true;
prev = pts + 2; closed = false;
pos += currLen;
pts += 3; pts += 3;
break; break;
} }
@ -2522,16 +2494,16 @@ void PathTrim::trimPath(const PathCommand* cmds, uint32_t cmd_count, const Point
if (!hasLineTo) { if (!hasLineTo) {
closed = true; closed = true;
pushedMoveTo = false; pushedMoveTo = false;
prev = begin = nullptr; prev = begin = zero;
break; break;
} }
if (*prev == *begin) { if (prev == begin) {
prev = begin = nullptr; prev = begin = zero;
closed = true; closed = true;
pushedMoveTo = false; pushedMoveTo = false;
break; break;
} }
auto currLen = length(prev, begin); auto currLen = length(&prev, &begin);
if (currLen + pos < start) { if (currLen + pos < start) {
pos += currLen; pos += currLen;
break; break;
@ -2541,10 +2513,10 @@ void PathTrim::trimPath(const PathCommand* cmds, uint32_t cmd_count, const Point
pos += currLen; pos += currLen;
break; break;
} }
handle_line_to(prev, begin); // handle_line_to(&prev, &begin);
closed = true; closed = true;
pushedMoveTo = false; pushedMoveTo = false;
prev = begin = nullptr; prev = begin = zero;
pos += currLen; pos += currLen;
break; break;
} }

View file

@ -116,6 +116,7 @@ public:
RenderRegion bounds() const; RenderRegion bounds() const;
private: private:
void doTrimStroke(const PathCommand* cmds, uint32_t cmd_count, const Point* pts, uint32_t pts_count, bool simultaneous, float start, float end);
void doStroke(const PathCommand* cmds, uint32_t cmd_count, const Point* pts, uint32_t pts_count); void doStroke(const PathCommand* cmds, uint32_t cmd_count, const Point* pts, uint32_t pts_count);
void doDashStroke(const PathCommand* cmds, uint32_t cmd_count, const Point* pts, uint32_t pts_count, void doDashStroke(const PathCommand* cmds, uint32_t cmd_count, const Point* pts, uint32_t pts_count,
uint32_t dash_count, const float* dash_pattern); uint32_t dash_count, const float* dash_pattern);