diff --git a/src/renderer/gl_engine/tvgGlGeometry.cpp b/src/renderer/gl_engine/tvgGlGeometry.cpp index 3f1c932a..e56f9d3e 100644 --- a/src/renderer/gl_engine/tvgGlGeometry.cpp +++ b/src/renderer/gl_engine/tvgGlGeometry.cpp @@ -28,18 +28,16 @@ bool GlGeometry::tesselate(const RenderShape& rshape, RenderUpdateFlag flag) { - const RenderPath* path = nullptr; - RenderPath trimmedPath; - if (rshape.trimpath()) { - if (!rshape.stroke->trim.trim(rshape.path, trimmedPath)) return true; - path = &trimmedPath; - } else path = &rshape.path; - if (flag & (RenderUpdateFlag::Color | RenderUpdateFlag::Gradient | RenderUpdateFlag::Transform | RenderUpdateFlag::Path)) { fill.clear(); BWTessellator bwTess{&fill}; - bwTess.tessellate(*path, matrix); + if (rshape.trimpath()) { + RenderPath trimmedPath; + if (rshape.stroke->trim.trim(rshape.path, trimmedPath)) bwTess.tessellate(trimmedPath, matrix); + else return true; + } else bwTess.tessellate(rshape.path, matrix); + fillRule = rshape.rule; bounds = bwTess.bounds(); } @@ -48,7 +46,7 @@ bool GlGeometry::tesselate(const RenderShape& rshape, RenderUpdateFlag flag) stroke.clear(); Stroker stroker{&stroke, matrix}; - stroker.stroke(&rshape, *path); + stroker.stroke(&rshape); bounds = stroker.bounds(); } diff --git a/src/renderer/gl_engine/tvgGlTessellator.cpp b/src/renderer/gl_engine/tvgGlTessellator.cpp index 459c7cf8..ad7357ae 100644 --- a/src/renderer/gl_engine/tvgGlTessellator.cpp +++ b/src/renderer/gl_engine/tvgGlTessellator.cpp @@ -39,7 +39,7 @@ Stroker::Stroker(GlGeometryBuffer* buffer, const Matrix& matrix) : mBuffer(buffe } -void Stroker::stroke(const RenderShape *rshape, const RenderPath& path) +void Stroker::stroke(const RenderShape *rshape) { mMiterLimit = rshape->strokeMiterlimit(); mStrokeCap = rshape->strokeCap(); @@ -52,9 +52,12 @@ void Stroker::stroke(const RenderShape *rshape, const RenderPath& path) mStrokeWidth = strokeWidth / mMatrix.e11; } - auto& dash = rshape->stroke->dash; - if (dash.length < DASH_PATTERN_THRESHOLD) doStroke(path); - else doDashStroke(path, dash.pattern, dash.count, dash.offset, dash.length); + RenderPath dashed; + if (rshape->strokeDash(dashed)) doStroke(dashed); + else if (rshape->trimpath()) { + RenderPath trimmedPath; + if (rshape->stroke->trim.trim(rshape->path, trimmedPath)) doStroke(trimmedPath); + } else doStroke(rshape->path); } @@ -109,19 +112,6 @@ void Stroker::doStroke(const RenderPath& path) } -void Stroker::doDashStroke(const RenderPath& path, const float *patterns, uint32_t patternCnt, float offset, float length) -{ - RenderPath dpath; - - dpath.cmds.reserve(20 * path.cmds.count); - dpath.pts.reserve(20 * path.pts.count); - - DashStroke dash(&dpath.cmds, &dpath.pts, patterns, patternCnt, offset, length); - dash.doStroke(path, mStrokeCap != StrokeCap::Butt); - doStroke(dpath); -} - - void Stroker::strokeCap() { if (mStrokeCap == StrokeCap::Butt) return; @@ -442,227 +432,6 @@ void Stroker::strokeRound(const Point& p, const Point& outDir) } -DashStroke::DashStroke(Array *cmds, Array *pts, const float *patterns, uint32_t patternCnt, float offset, float length) - : mCmds(cmds), - mPts(pts), - mDashPattern(patterns), - mDashCount(patternCnt), - mDashOffset(offset), - mDashLength(length) -{ -} - - -void DashStroke::doStroke(const RenderPath& path, bool validPoint) -{ - //validPoint: zero length segment with non-butt cap still should be rendered as a point - only the caps are visible - int32_t idx = 0; - auto offset = mDashOffset; - bool gap = false; - if (!tvg::zero(mDashOffset)) { - auto length = (mDashCount % 2) ? mDashLength * 2 : mDashLength; - offset = fmodf(offset, length); - if (offset < 0) offset += length; - - for (uint32_t i = 0; i < mDashCount * (mDashCount % 2 + 1); ++i, ++idx) { - auto curPattern = mDashPattern[i % mDashCount]; - if (offset < curPattern) break; - offset -= curPattern; - gap = !gap; - } - idx = idx % mDashCount; - } - - auto pts = path.pts.data; - ARRAY_FOREACH(cmd, path.cmds) { - switch (*cmd) { - case PathCommand::Close: { - this->dashLineTo(mPtStart, validPoint); - break; - } - case PathCommand::MoveTo: { - // reset the dash state - mCurrIdx = idx; - mCurrLen = mDashPattern[idx] - offset; - mCurOpGap = gap; - mMove = true; - mPtStart = mPtCur = *pts; - pts++; - break; - } - case PathCommand::LineTo: { - this->dashLineTo(*pts, validPoint); - pts++; - break; - } - case PathCommand::CubicTo: { - this->dashCubicTo(pts[0], pts[1], pts[2], validPoint); - pts += 3; - break; - } - default: break; - } - } -} - - -void DashStroke::drawPoint(const Point& p) -{ - if (mMove || mDashPattern[mCurrIdx] < FLOAT_EPSILON) { - this->moveTo(p); - mMove = false; - } - this->lineTo(p); -} - - -void DashStroke::dashLineTo(const Point& to, bool validPoint) -{ - auto len = length(mPtCur - to); - - if (tvg::zero(len)) { - this->moveTo(mPtCur); - } else if (len <= mCurrLen) { - mCurrLen -= len; - if (!mCurOpGap) { - if (mMove) { - this->moveTo(mPtCur); - mMove = false; - } - this->lineTo(to); - } - } else { - Line curr = {mPtCur, to}; - - while (len - mCurrLen > DASH_PATTERN_THRESHOLD) { - Line right; - if (mCurrLen > 0.0f) { - Line left; - curr.split(mCurrLen, left, right); - len -= mCurrLen; - if (!mCurOpGap) { - if (mMove || mDashPattern[mCurrIdx] - mCurrLen < FLOAT_EPSILON) { - this->moveTo(left.pt1); - mMove = false; - } - this->lineTo(left.pt2); - } - } else { - if (validPoint && !mCurOpGap) drawPoint(curr.pt1); - right = curr; - } - mCurrIdx = (mCurrIdx + 1) % mDashCount; - mCurrLen = mDashPattern[mCurrIdx]; - mCurOpGap = !mCurOpGap; - curr = right; - mPtCur = curr.pt1; - mMove = true; - } - mCurrLen -= len; - if (!mCurOpGap) { - if (mMove) { - this->moveTo(curr.pt1); - mMove = false; - } - this->lineTo(curr.pt2); - } - - if (mCurrLen < 0.1f) { - mCurrIdx = (mCurrIdx + 1) % mDashCount; - mCurrLen = mDashPattern[mCurrIdx]; - mCurOpGap = !mCurOpGap; - } - } - - mPtCur = to; -} - - -void DashStroke::dashCubicTo(const Point& cnt1, const Point& cnt2, const Point& end, bool validPoint) -{ - Bezier cur{ mPtCur, cnt1, cnt2, end }; - - auto len = cur.length(); - - if (tvg::zero(len)) { - this->moveTo(mPtCur); - } else if (len <= mCurrLen) { - mCurrLen -= len; - if (!mCurOpGap) { - if (mMove) { - this->moveTo(mPtCur); - mMove = false; - } - this->cubicTo(cnt1, cnt2, end); - } - } else { - while (len - mCurrLen > DASH_PATTERN_THRESHOLD) { - Bezier right; - if (mCurrLen > 0.0f) { - Bezier left; - cur.split(mCurrLen, left, right); - len -= mCurrLen; - if (!mCurOpGap) { - if (mMove || mDashPattern[mCurrIdx] - mCurrLen < FLOAT_EPSILON) { - this->moveTo(left.start); - mMove = false; - } - this->cubicTo(left.ctrl1, left.ctrl2, left.end); - } - } else { - if (validPoint && !mCurOpGap) drawPoint(cur.start); - right = cur; - } - mCurrIdx = (mCurrIdx + 1) % mDashCount; - mCurrLen = mDashPattern[mCurrIdx]; - mCurOpGap = !mCurOpGap; - cur = right; - mPtCur = cur.start; - mMove = true; - } - - mCurrLen -= len; - if (!mCurOpGap) { - if (mMove) { - this->moveTo(cur.start); - mMove = false; - } - this->cubicTo(cur.ctrl1, cur.ctrl2, cur.end); - } - - if (mCurrLen < 0.1f) { - mCurrIdx = (mCurrIdx + 1) % mDashCount; - mCurrLen = mDashPattern[mCurrIdx]; - mCurOpGap = !mCurOpGap; - } - } - mPtCur = end; -} - - -void DashStroke::moveTo(const Point& pt) -{ - mPts->push(pt); - mCmds->push(PathCommand::MoveTo); -} - - -void DashStroke::lineTo(const Point& pt) -{ - mPts->push(pt); - mCmds->push(PathCommand::LineTo); -} - - -void DashStroke::cubicTo(const Point& cnt1, const Point& cnt2, const Point& end) -{ - mPts->push(cnt1); - mPts->push(cnt2); - mPts->push(end); - mCmds->push(PathCommand::CubicTo); -} - - BWTessellator::BWTessellator(GlGeometryBuffer* buffer): mBuffer(buffer) { } diff --git a/src/renderer/gl_engine/tvgGlTessellator.h b/src/renderer/gl_engine/tvgGlTessellator.h index 85472281..7e2d96f5 100644 --- a/src/renderer/gl_engine/tvgGlTessellator.h +++ b/src/renderer/gl_engine/tvgGlTessellator.h @@ -39,12 +39,11 @@ class Stroker }; public: Stroker(GlGeometryBuffer* buffer, const Matrix& matrix); - void stroke(const RenderShape *rshape, const RenderPath& path); + void stroke(const RenderShape *rshape); RenderRegion bounds() const; private: void doStroke(const RenderPath& path); - void doDashStroke(const RenderPath& path, const float* patterns, uint32_t patternCnt, float offset, float length); float strokeRadius() const { @@ -75,34 +74,6 @@ private: Point mRightBottom = {0.0f, 0.0f}; }; -class DashStroke -{ -public: - DashStroke(Array* cmds, Array* pts, const float* patterns, uint32_t patternCnt, float offset, float length); - void doStroke(const RenderPath& path, bool drawPoint); - -private: - void drawPoint(const Point& p); - void dashLineTo(const Point& pt, bool drawPoint); - void dashCubicTo(const Point& pt1, const Point& pt2, const Point& pt3, bool drawPoint); - void moveTo(const Point& pt); - void lineTo(const Point& pt); - void cubicTo(const Point& pt1, const Point& pt2, const Point& pt3); - - Array* mCmds; - Array* mPts; - const float* mDashPattern; - uint32_t mDashCount; - float mDashOffset; - float mDashLength; - float mCurrLen = 0.0f; - int32_t mCurrIdx = 0; - bool mCurOpGap = false; - bool mMove = true; - Point mPtStart = {}; - Point mPtCur = {}; -}; - class BWTessellator { public: diff --git a/src/renderer/tvgRender.cpp b/src/renderer/tvgRender.cpp index 392da4aa..fb887a54 100644 --- a/src/renderer/tvgRender.cpp +++ b/src/renderer/tvgRender.cpp @@ -587,4 +587,210 @@ bool RenderTrimPath::trim(const RenderPath& in, RenderPath& out) const } return out.pts.count >= 2; +} + +/************************************************************************/ +/* StrokeDashPath Class Implementation */ +/************************************************************************/ + +//TODO: use this common function from all engines +#ifdef THORVG_GL_RASTER_SUPPORT + +struct StrokeDashPath +{ +public: + StrokeDashPath(RenderStroke::Dash dash) : dash(dash) {} + bool gen(const RenderPath& in, RenderPath& out, bool drawPoint); + +private: + void lineTo(RenderPath& out, const Point& pt, bool drawPoint); + void cubicTo(RenderPath& out, const Point& pt1, const Point& pt2, const Point& pt3, bool drawPoint); + void point(RenderPath& out, const Point& p); + + template + void segment(Segment seg, float len, RenderPath& out, bool allowDot, LengthFn lengthFn, SplitFn splitFn, DrawFn drawFn, PointFn getStartPt, const Point& endPos); + + RenderStroke::Dash dash; + float curLen = 0.0f; + int32_t curIdx = 0; + Point curPos{}; + bool opGap = false; + bool move = true; +}; + + +template +void StrokeDashPath::segment(Segment seg, float len, RenderPath& out, bool allowDot, LengthFn lengthFn, SplitFn splitFn, DrawFn drawFn, PointFn getStartPt, const Point& end) +{ + #define MIN_CURR_LEN_THRESHOLD 0.1f + + if (tvg::zero(len)) { + out.moveTo(curPos); + } else if (len <= curLen) { + curLen -= len; + if (!opGap) { + if (move) { + out.moveTo(curPos); + move = false; + } + drawFn(seg); + } + } else { + Segment left, right; + while (len - curLen > DASH_PATTERN_THRESHOLD) { + if (curLen > 0.0f) { + splitFn(seg, curLen, left, right); + len -= curLen; + if (!opGap) { + if (move || dash.pattern[curIdx] - curLen < FLOAT_EPSILON) { + out.moveTo(getStartPt(left)); + move = false; + } + drawFn(left); + } + } else { + if (allowDot && !opGap) point(out, getStartPt(seg)); + right = seg; + } + + curIdx = (curIdx + 1) % dash.count; + curLen = dash.pattern[curIdx]; + opGap = !opGap; + seg = right; + curPos = getStartPt(seg); + move = true; + } + curLen -= len; + if (!opGap) { + if (move) { + out.moveTo(getStartPt(seg)); + move = false; + } + drawFn(seg); + } + if (curLen < MIN_CURR_LEN_THRESHOLD) { + curIdx = (curIdx + 1) % dash.count; + curLen = dash.pattern[curIdx]; + opGap = !opGap; + } + } + curPos = end; +} + + +//allowDot: zero length segment with non-butt cap still should be rendered as a point - only the caps are visible +bool StrokeDashPath::gen(const RenderPath& in, RenderPath& out, bool allowDot) +{ + int32_t idx = 0; + auto offset = dash.offset; + auto gap = false; + if (!tvg::zero(dash.offset)) { + auto length = (dash.count % 2) ? dash.length * 2 : dash.length; + offset = fmodf(offset, length); + if (offset < 0) offset += length; + + for (uint32_t i = 0; i < dash.count * (dash.count % 2 + 1); ++i, ++idx) { + auto curPattern = dash.pattern[i % dash.count]; + if (offset < curPattern) break; + offset -= curPattern; + gap = !gap; + } + idx = idx % dash.count; + } + + auto pts = in.pts.data; + Point start{}; + + ARRAY_FOREACH(cmd, in.cmds) { + switch (*cmd) { + case PathCommand::Close: { + lineTo(out, start, allowDot); + break; + } + case PathCommand::MoveTo: { + // reset the dash state + curIdx = idx; + curLen = dash.pattern[idx] - offset; + opGap = gap; + move = true; + start = curPos = *pts; + pts++; + break; + } + case PathCommand::LineTo: { + lineTo(out, *pts, allowDot); + pts++; + break; + } + case PathCommand::CubicTo: { + cubicTo(out, pts[0], pts[1], pts[2], allowDot); + pts += 3; + break; + } + default: break; + } + } + return true; +} + + +void StrokeDashPath::point(RenderPath& out, const Point& p) +{ + if (move || dash.pattern[curIdx] < FLOAT_EPSILON) { + out.moveTo(p); + move = false; + } + out.lineTo(p); +} + + +void StrokeDashPath::lineTo(RenderPath& out, const Point& to, bool allowDot) +{ + Line line = {curPos, to}; + auto len = length(to - curPos); + segment(line, len, out, allowDot, + [](const Line& l) { return length(l.pt2 - l.pt1); }, + [](const Line& l, float len, Line& left, Line& right) { l.split(len, left, right); }, + [&](const Line& l) { out.lineTo(l.pt2); }, + [](const Line& l) { return l.pt1; }, + to + ); +} + + +void StrokeDashPath::cubicTo(RenderPath& out, const Point& cnt1, const Point& cnt2, const Point& end, bool allowDot) +{ + Bezier curve = {curPos, cnt1, cnt2, end}; + auto len = curve.length(); + segment(curve, len, out, allowDot, + [](const Bezier& b) { return b.length(); }, + [](const Bezier& b, float len, Bezier& left, Bezier& right) { b.split(len, left, right); }, + [&](const Bezier& b) { out.cubicTo(b.ctrl1, b.ctrl2, b.end); }, + [](const Bezier& b) { return b.start; }, + end + ); +} +#endif + +bool RenderShape::strokeDash(RenderPath& out) const +{ + if (!stroke || stroke->dash.count == 0 || stroke->dash.length < DASH_PATTERN_THRESHOLD) return false; + +//TODO: use this common function from all engines +#ifdef THORVG_GL_RASTER_SUPPORT + out.cmds.reserve(20 * path.cmds.count); + out.pts.reserve(20 * path.pts.count); + + StrokeDashPath dash(stroke->dash); + auto allowDot = stroke->cap != StrokeCap::Butt; + + if (trimpath()) { + RenderPath tpath; + if (stroke->trim.trim(path, tpath)) return dash.gen(tpath, out, allowDot); + else return false; + } + return dash.gen(path, out, allowDot); +#else + return false; +#endif } \ No newline at end of file diff --git a/src/renderer/tvgRender.h b/src/renderer/tvgRender.h index 4eb26a9a..ee9c1cd8 100644 --- a/src/renderer/tvgRender.h +++ b/src/renderer/tvgRender.h @@ -233,6 +233,31 @@ struct RenderPath cmds.clear(); } + void close() + { + cmds.push(PathCommand::Close); + } + + void moveTo(const Point& pt) + { + pts.push(pt); + cmds.push(PathCommand::MoveTo); + } + + void lineTo(const Point& pt) + { + pts.push(pt); + cmds.push(PathCommand::LineTo); + } + + void cubicTo(const Point& cnt1, const Point& cnt2, const Point& end) + { + pts.push(cnt1); + pts.push(cnt2); + pts.push(end); + cmds.push(PathCommand::CubicTo); + } + bool bounds(Matrix* m, float* x, float* y, float* w, float* h); }; @@ -256,7 +281,7 @@ struct RenderStroke float width = 0.0f; RenderColor color{}; Fill *fill = nullptr; - struct { + struct Dash { float* pattern = nullptr; uint32_t count = 0; float offset = 0.0f; @@ -383,6 +408,8 @@ struct RenderShape if (!stroke) return 4.0f; return stroke->miterlimit;; } + + bool strokeDash(RenderPath& out) const; }; struct RenderEffect