From 9aa2566b45f57c742f134a6e7f4597c7011406fc Mon Sep 17 00:00:00 2001 From: Hermet Park Date: Wed, 3 Jun 2020 20:50:13 +0900 Subject: [PATCH] sw_engine: support stroke dash feature Change-Id: Ibed8bcb6a07952a059bb9a7355f7c43db97aa672 --- inc/tizenvg.h | 4 +- src/lib/sw_engine/tvgSwCommon.h | 40 +-- src/lib/sw_engine/tvgSwMath.cpp | 33 +-- src/lib/sw_engine/tvgSwRenderer.cpp | 37 ++- src/lib/sw_engine/tvgSwShape.cpp | 482 ++++++++++++++++++++++++++++++------ src/lib/sw_engine/tvgSwStroke.cpp | 33 ++- src/lib/tvgShape.cpp | 4 +- src/lib/tvgShapeImpl.h | 11 +- test/testStrokeLine.cpp | 49 ++++ 9 files changed, 533 insertions(+), 160 deletions(-) diff --git a/inc/tizenvg.h b/inc/tizenvg.h index 905c3f0..18db842 100644 --- a/inc/tizenvg.h +++ b/inc/tizenvg.h @@ -143,7 +143,7 @@ public: //Stroke int stroke(float width) noexcept; int stroke(uint8_t r, uint8_t g, uint8_t b, uint8_t a) noexcept; - int stroke(const size_t* dashPattern, size_t cnt) noexcept; + int stroke(const float* dashPattern, size_t cnt) noexcept; int stroke(StrokeCap cap) noexcept; int stroke(StrokeJoin join) noexcept; @@ -163,7 +163,7 @@ public: float strokeWidth() const noexcept; int strokeColor(uint8_t* r, uint8_t* g, uint8_t* b, uint8_t* a) const noexcept; - size_t strokeDash(const size_t** dashPattern) const noexcept; + size_t strokeDash(const float** dashPattern) const noexcept; StrokeCap strokeCap() const noexcept; StrokeJoin strokeJoin() const noexcept; diff --git a/src/lib/sw_engine/tvgSwCommon.h b/src/lib/sw_engine/tvgSwCommon.h index cfb15bd..16d4c1e 100644 --- a/src/lib/sw_engine/tvgSwCommon.h +++ b/src/lib/sw_engine/tvgSwCommon.h @@ -125,7 +125,8 @@ struct SwStroke SwFixed angleOut; SwPoint center; SwFixed lineLength; - SwPoint subPathStart; + SwFixed subPathAngle; + SwPoint ptStartSubPath; SwFixed subPathLineLength; SwFixed width; @@ -136,11 +137,22 @@ struct SwStroke SwStrokeBorder borders[2]; bool firstPt; - bool subPathOpen; - bool subPathAngle; + bool openSubPath; bool handleWideStrokes; }; +struct SwDashStroke +{ + SwOutline* outline; + int32_t curLen; + int32_t curIdx; + Point ptStart; + Point ptCur; + float* pattern; + size_t cnt; + bool curOpGap; +}; + struct SwShape { SwOutline* outline; @@ -183,18 +195,16 @@ SwFixed mathLength(SwPoint& pt); bool mathSmallCubic(SwPoint* base, SwFixed& angleIn, SwFixed& angleMid, SwFixed& angleOut); SwFixed mathMean(SwFixed angle1, SwFixed angle2); -void shapeReset(SwShape& sdata); -bool shapeGenOutline(const Shape& shape, SwShape& sdata); -bool shapeGenRle(const Shape& shape, SwShape& sdata, const SwSize& clip); -void shapeDelOutline(SwShape& sdata); -void shapeResetStroke(const Shape& shape, SwShape& sdata); -bool shapeGenStrokeOutline(const Shape& shape, SwShape& sdata); -bool shapeGenStrokeRle(const Shape& shape, SwShape& sdata, const SwSize& clip); -void shapeTransformOutline(const Shape& shape, SwShape& sdata, const RenderTransform& transform); -void shapeFree(SwShape* sdata); - -void strokeReset(SwStroke& stroke, float width, StrokeCap cap, StrokeJoin join); -bool strokeParseOutline(SwStroke& stroke, SwOutline& outline); +void shapeReset(SwShape& shape); +bool shapeGenOutline(SwShape& shape, const Shape& sdata); +bool shapeGenRle(SwShape& shape, const Shape& sdata, const SwSize& clip, const RenderTransform* transform); +void shapeDelOutline(SwShape& shape); +void shapeResetStroke(SwShape& shape, const Shape& sdata); +bool shapeGenStrokeRle(SwShape& shape, const Shape& sdata, const SwSize& clip); +void shapeFree(SwShape* shape); + +void strokeReset(SwStroke& stroke, const Shape& shape); +bool strokeParseOutline(SwStroke& stroke, const SwOutline& outline); SwOutline* strokeExportOutline(SwStroke& stroke); void strokeFree(SwStroke* stroke); diff --git a/src/lib/sw_engine/tvgSwMath.cpp b/src/lib/sw_engine/tvgSwMath.cpp index 80c7f8e..a0a0ea1 100644 --- a/src/lib/sw_engine/tvgSwMath.cpp +++ b/src/lib/sw_engine/tvgSwMath.cpp @@ -140,20 +140,21 @@ static void _polarize(SwPoint& pt) static void _rotate(SwPoint& pt, SwFixed theta) { - auto v = pt; + SwFixed x = pt.x; + SwFixed y = pt.y; //Rotate inside [-PI/4, PI/4] sector while (theta < -ANGLE_PI4) { - auto tmp = v.y; - v.y = -v.x; - v.x = tmp; + auto tmp = y; + y = -x; + x = tmp; theta += ANGLE_PI2; } while (theta > ANGLE_PI4) { - auto tmp = -v.y; - v.y = v.x; - v.x = tmp; + auto tmp = -y; + y = x; + x = tmp; theta -= ANGLE_PI2; } @@ -163,19 +164,19 @@ static void _rotate(SwPoint& pt, SwFixed theta) for (i = 1, j = 1; i < ATAN_MAX; j <<= 1, ++i) { if (theta < 0) { - auto tmp = v.x + ((v.y + j) >> i); - v.y = v.y - ((v.x + j) >> i); - v.x = tmp; + auto tmp = x + ((y + j) >> i); + y = y - ((x + j) >> i); + x = tmp; theta += *atan++; }else { - auto tmp = v.x - ((v.y + j) >> i); - v.y = v.y + ((v.x + j) >> i); - v.x = tmp; + auto tmp = x - ((y + j) >> i); + y = y + ((x + j) >> i); + x = tmp; theta -= *atan++; } } - pt = v; + pt = {x, y}; } @@ -305,9 +306,9 @@ void mathRotate(SwPoint& pt, SwFixed angle) auto v = pt; auto shift = _normalize(v); - auto theta = angle; - _rotate(v, theta); + auto theta = angle; + _rotate(v, theta); v.x = _downscale(v.x); v.y = _downscale(v.y); diff --git a/src/lib/sw_engine/tvgSwRenderer.cpp b/src/lib/sw_engine/tvgSwRenderer.cpp index 38ac674..5461abe 100644 --- a/src/lib/sw_engine/tvgSwRenderer.cpp +++ b/src/lib/sw_engine/tvgSwRenderer.cpp @@ -82,16 +82,16 @@ bool SwRenderer::dispose(const Shape& shape, void *data) return true; } -void* SwRenderer::prepare(const Shape& shape, void* data, const RenderTransform* transform, RenderUpdateFlag flags) +void* SwRenderer::prepare(const Shape& sdata, void* data, const RenderTransform* transform, RenderUpdateFlag flags) { //prepare shape data - SwShape* sdata = static_cast(data); - if (!sdata) { - sdata = static_cast(calloc(1, sizeof(SwShape))); - assert(sdata); + auto shape = static_cast(data); + if (!shape) { + shape = static_cast(calloc(1, sizeof(SwShape))); + assert(shape); } - if (flags == RenderUpdateFlag::None) return sdata; + if (flags == RenderUpdateFlag::None) return shape; //TODO: Threading @@ -99,34 +99,29 @@ void* SwRenderer::prepare(const Shape& shape, void* data, const RenderTransform* //Shape if (flags & (RenderUpdateFlag::Path | RenderUpdateFlag::Transform)) { - + shapeReset(*shape); uint8_t alpha = 0; - shape.fill(nullptr, nullptr, nullptr, &alpha); - + sdata.fill(nullptr, nullptr, nullptr, &alpha); if (alpha > 0) { - shapeReset(*sdata); - if (!shapeGenOutline(shape, *sdata)) return sdata; - if (transform) shapeTransformOutline(shape, *sdata, *transform); - if (!shapeGenRle(shape, *sdata, clip)) return sdata; + if (!shapeGenRle(*shape, sdata, clip, transform)) return shape; } } //Stroke if (flags & (RenderUpdateFlag::Stroke | RenderUpdateFlag::Transform)) { - - if (shape.strokeWidth() > 0.5) { - shapeResetStroke(shape, *sdata); + if (sdata.strokeWidth() > 0.5) { + shapeResetStroke(*shape, sdata); uint8_t alpha = 0; - shape.strokeColor(nullptr, nullptr, nullptr, &alpha); + sdata.strokeColor(nullptr, nullptr, nullptr, &alpha); if (alpha > 0) { - if (!shapeGenStrokeRle(shape, *sdata, clip)) return sdata; + if (!shapeGenStrokeRle(*shape, sdata, clip)) return shape; } } } - shapeDelOutline(*sdata); + shapeDelOutline(*shape); - return sdata; + return shape; } @@ -159,4 +154,4 @@ SwRenderer* SwRenderer::inst() return dynamic_cast(RenderInitializer::inst(renderInit)); } -#endif /* _TVG_SW_RENDERER_CPP_ */ \ No newline at end of file +#endif /* _TVG_SW_RENDERER_CPP_ */ diff --git a/src/lib/sw_engine/tvgSwShape.cpp b/src/lib/sw_engine/tvgSwShape.cpp index 30b79c8..dcb57c7 100644 --- a/src/lib/sw_engine/tvgSwShape.cpp +++ b/src/lib/sw_engine/tvgSwShape.cpp @@ -23,48 +23,164 @@ /* Internal Class Implementation */ /************************************************************************/ -static void _growOutlineContour(SwOutline& outline, uint32_t n) +struct Line { - if (n == 0) { - free(outline.cntrs); - outline.cntrs = nullptr; - outline.cntrsCnt = 0; - outline.reservedCntrsCnt = 0; - return; + Point pt1; + Point pt2; +}; + + +struct Bezier +{ + Point start; + Point ctrl1; + Point ctrl2; + Point end; +}; + + +static float _lineLength(const Point& pt1, const Point& pt2) +{ + /* approximate sqrt(x*x + y*y) using alpha max plus beta min algorithm. + With alpha = 1, beta = 3/8, giving results with the largest error less + than 7% compared to the exact value. */ + Point diff = {pt2.x - pt1.x, pt2.y - pt1.y}; + if (diff.x < 0) diff.x = -diff.x; + if (diff.y < 0) diff.y = -diff.y; + return (diff.x > diff.y) ? (diff.x + diff.y * 0.375f) : (diff.y + diff.x * 0.375f); +} + + +static void _lineSplitAt(const Line& cur, float at, Line& left, Line& right) +{ + auto len = _lineLength(cur.pt1, cur.pt2); + auto dx = ((cur.pt2.x - cur.pt1.x) / len) * at; + auto dy = ((cur.pt2.y - cur.pt1.y) / len) * at; + left.pt1 = cur.pt1; + left.pt2.x = left.pt1.x + dx; + left.pt2.y = left.pt1.y + dy; + right.pt1 = left.pt2; + right.pt2 = cur.pt2; +} + + +static void _bezSplit(const Bezier&cur, Bezier& left, Bezier& right) +{ + auto c = (cur.ctrl1.x + cur.ctrl2.x) * 0.5f; + left.ctrl1.x = (cur.start.x + cur.ctrl1.x) * 0.5f; + right.ctrl2.x = (cur.ctrl2.x + cur.end.x) * 0.5f; + left.start.x = cur.start.x; + right.end.x = cur.end.x; + left.ctrl2.x = (left.ctrl1.x + c) * 0.5f; + right.ctrl1.x = (right.ctrl2.x + c) * 0.5f; + left.end.x = right.start.x = (left.ctrl2.x + right.ctrl1.x) * 0.5f; + + c = (cur.ctrl1.y + cur.ctrl2.y) * 0.5f; + left.ctrl1.y = (cur.start.y + cur.ctrl1.y) * 0.5f; + right.ctrl2.y = (cur.ctrl2.y + cur.end.y) * 0.5f; + left.start.y = cur.start.y; + right.end.y = cur.end.y; + left.ctrl2.y = (left.ctrl1.y + c) * 0.5f; + right.ctrl1.y = (right.ctrl2.y + c) * 0.5f; + left.end.y = right.start.y = (left.ctrl2.y + right.ctrl1.y) * 0.5f; +} + + +static float _bezLength(const Bezier& cur) +{ + Bezier left, right; + auto len = _lineLength(cur.start, cur.ctrl1) + _lineLength(cur.ctrl1, cur.ctrl2) + _lineLength(cur.ctrl2, cur.end); + auto chord = _lineLength(cur.start, cur.end); + + if (fabs(len - chord) > FLT_EPSILON) { + _bezSplit(cur, left, right); + return _bezLength(left) + _bezLength(right); } - if (outline.reservedCntrsCnt >= outline.cntrsCnt + n) return; + return len; +} - //cout << "Grow Cntrs: " << outline.reservedCntrsCnt << " -> " << outline.cntrsCnt + n << endl;; - outline.reservedCntrsCnt = n; - outline.cntrs = static_cast(realloc(outline.cntrs, n * sizeof(uint32_t))); - assert(outline.cntrs); + +static void _bezSplitLeft(Bezier& cur, float at, Bezier& left) +{ + left.start = cur.start; + + left.ctrl1.x = cur.start.x + at * (cur.ctrl1.x - cur.start.x); + left.ctrl1.y = cur.start.y + at * (cur.ctrl1.y - cur.start.y); + + left.ctrl2.x = cur.ctrl1.x + at * (cur.ctrl2.x - cur.ctrl1.x); // temporary holding spot + left.ctrl2.y = cur.ctrl1.y + at * (cur.ctrl2.y - cur.ctrl1.y); // temporary holding spot + + cur.ctrl2.x = cur.ctrl2.x + at * (cur.end.x - cur.ctrl2.x); + cur.ctrl2.y = cur.ctrl2.y + at * (cur.end.y - cur.ctrl2.y); + + cur.ctrl1.x = left.ctrl2.x + at * (cur.ctrl2.x - left.ctrl2.x); + cur.ctrl1.y = left.ctrl2.y + at * (cur.ctrl2.y - left.ctrl2.y); + + left.ctrl2.x = left.ctrl1.x + at * (left.ctrl2.x - left.ctrl1.x); + left.ctrl2.y = left.ctrl1.y + at * (left.ctrl2.y - left.ctrl1.y); + + left.end.x = cur.start.x = left.ctrl2.x + at * (cur.ctrl1.x - left.ctrl2.x); + left.end.y = cur.start.y = left.ctrl2.y + at * (cur.ctrl1.y - left.ctrl2.y); } -static void _growOutlinePoint(SwOutline& outline, uint32_t n) +static float _bezAt(const Bezier& bz, float at) { - if (n == 0) { - free(outline.pts); - outline.pts = nullptr; - free(outline.types); - outline.types = nullptr; - outline.reservedPtsCnt = 0; - outline.ptsCnt = 0; - return; + auto len = _bezLength(bz); + auto biggest = 1.0f; + + if (at >= len) return 1.0f; + + at *= 0.5f; + + while (true) { + auto right = bz; + Bezier left; + _bezSplitLeft(right, at, left); + auto len2 = _bezLength(left); + + if (fabs(len2 - len) < FLT_EPSILON) break; + + if (len2 < len) { + at += (biggest - at) * 0.5f; + } else { + biggest = at; + at -= (at * 0.5f); + } } + return at; +} - if (outline.reservedPtsCnt >= outline.ptsCnt + n) return; - //cout << "Grow Pts: " << outline.reservedPtsCnt << " -> " << outline.ptsCnt + n << endl; - outline.reservedPtsCnt = n; - outline.pts = static_cast(realloc(outline.pts, n * sizeof(SwPoint))); +static void _bezSplitAt(const Bezier& cur, float at, Bezier& left, Bezier& right) +{ + right = cur; + auto t = _bezAt(right, at); + _bezSplitLeft(right, t, left); +} + + +static void _growOutlineContour(SwOutline& outline, uint32_t n) +{ + if (outline.reservedCntrsCnt >= outline.cntrsCnt + n) return; + outline.reservedCntrsCnt = outline.cntrsCnt + n; + outline.cntrs = static_cast(realloc(outline.cntrs, outline.reservedCntrsCnt * sizeof(uint32_t))); + assert(outline.cntrs); +} + + +static void _growOutlinePoint(SwOutline& outline, uint32_t n) +{ + if (outline.reservedPtsCnt >= outline.ptsCnt + n) return; + outline.reservedPtsCnt = outline.ptsCnt + n; + outline.pts = static_cast(realloc(outline.pts, outline.reservedPtsCnt * sizeof(SwPoint))); assert(outline.pts); - outline.types = static_cast(realloc(outline.types, n * sizeof(uint8_t))); + outline.types = static_cast(realloc(outline.types, outline.reservedPtsCnt * sizeof(uint8_t))); assert(outline.types); } -static void _freeOutline(SwOutline* outline) +static void _delOutline(SwOutline* outline) { if (!outline) return; @@ -112,7 +228,6 @@ static void _outlineLineTo(SwOutline& outline, const Point* to) outline.pts[outline.ptsCnt] = TO_SWPOINT(to); outline.types[outline.ptsCnt] = SW_CURVE_TYPE_POINT; - ++outline.ptsCnt; } @@ -208,80 +323,271 @@ static bool _updateBBox(SwOutline* outline, SwBBox& bbox) } -static bool _checkValid(SwShape& sdata, const SwSize& clip) +static bool _checkValid(const SwOutline* outline, const SwBBox& bbox, const SwSize& clip) { - assert(sdata.outline); + assert(outline); - if (sdata.outline->ptsCnt == 0 || sdata.outline->cntrsCnt <= 0) return false; + if (outline->ptsCnt == 0 || outline->cntrsCnt <= 0) return false; //Check boundary - if ((sdata.bbox.min.x > clip.w || sdata.bbox.min.y > clip.h) || - (sdata.bbox.min.x + sdata.bbox.max.x < 0) || - (sdata.bbox.min.y + sdata.bbox.max.y < 0)) return false; + if ((bbox.min.x > clip.w || bbox.min.y > clip.h) || + (bbox.min.x + bbox.max.x < 0) || + (bbox.min.y + bbox.max.y < 0)) return false; return true; } -/************************************************************************/ -/* External Class Implementation */ -/************************************************************************/ - -void shapeTransformOutline(const Shape& shape, SwShape& sdata, const RenderTransform& transform) +static void _transformOutline(SwOutline* outline, const RenderTransform* transform) { - auto outline = sdata.outline; assert(outline); + if (!transform) return; + for(uint32_t i = 0; i < outline->ptsCnt; ++i) { auto dx = static_cast(outline->pts[i].x >> 6); auto dy = static_cast(outline->pts[i].y >> 6); - auto tx = dx * transform.e11 + dy * transform.e12 + transform.e13; - auto ty = dx * transform.e21 + dy * transform.e22 + transform.e23; - auto pt = Point{tx + transform.e31, ty + transform.e32}; + auto tx = dx * transform->e11 + dy * transform->e12 + transform->e13; + auto ty = dx * transform->e21 + dy * transform->e22 + transform->e23; + auto pt = Point{tx + transform->e31, ty + transform->e32}; outline->pts[i] = TO_SWPOINT(&pt); } } -bool shapeGenRle(const Shape& shape, SwShape& sdata, const SwSize& clip) +static void _dashLineTo(SwDashStroke& dash, const Point* to) +{ + _growOutlinePoint(*dash.outline, dash.outline->ptsCnt >> 1); + _growOutlineContour(*dash.outline, dash.outline->cntrsCnt >> 1); + + Line cur = {dash.ptCur, *to}; + auto len = _lineLength(cur.pt1, cur.pt2); + + if (len < dash.curLen) { + dash.curLen -= len; + if (!dash.curOpGap) { + _outlineMoveTo(*dash.outline, &dash.ptCur); + _outlineLineTo(*dash.outline, to); + } + } else { + while (len > dash.curLen) { + len -= dash.curLen; + Line left, right; + _lineSplitAt(cur, dash.curLen, left, right);; + dash.curIdx = (dash.curIdx + 1) % dash.cnt; + if (!dash.curOpGap) { + _outlineMoveTo(*dash.outline, &left.pt1); + _outlineLineTo(*dash.outline, &left.pt2); + } + dash.curLen = dash.pattern[dash.curIdx]; + dash.curOpGap = !dash.curOpGap; + cur = right; + dash.ptCur = cur.pt1; + } + //leftovers + dash.curLen -= len; + if (!dash.curOpGap) { + _outlineMoveTo(*dash.outline, &cur.pt1); + _outlineLineTo(*dash.outline, &cur.pt2); + } + if (dash.curLen < 1) { + //move to next dash + dash.curIdx = (dash.curIdx + 1) % dash.cnt; + dash.curLen = dash.pattern[dash.curIdx]; + dash.curOpGap = !dash.curOpGap; + } + } + dash.ptCur = *to; +} + + +static void _dashCubicTo(SwDashStroke& dash, const Point* ctrl1, const Point* ctrl2, const Point* to) +{ + _growOutlinePoint(*dash.outline, dash.outline->ptsCnt >> 1); + _growOutlineContour(*dash.outline, dash.outline->cntrsCnt >> 1); + + Bezier cur = { dash.ptCur, *ctrl1, *ctrl2, *to}; + auto len = _bezLength(cur); + + if (len < dash.curLen) { + dash.curLen -= len; + if (!dash.curOpGap) { + _outlineMoveTo(*dash.outline, &dash.ptCur); + _outlineCubicTo(*dash.outline, ctrl1, ctrl2, to); + } + } else { + while (len > dash.curLen) { + Bezier left, right; + len -= dash.curLen; + _bezSplitAt(cur, dash.curLen, left, right); + dash.curIdx = (dash.curIdx + 1) % dash.cnt; + if (!dash.curOpGap) { + _outlineMoveTo(*dash.outline, &left.start); + _outlineCubicTo(*dash.outline, &left.ctrl1, &left.ctrl2, &left.end); + } + dash.curLen = dash.pattern[dash.curIdx]; + dash.curOpGap = !dash.curOpGap; + cur = right; + dash.ptCur = right.start; + } + //leftovers + dash.curLen -= len; + if (!dash.curOpGap) { + _outlineMoveTo(*dash.outline, &cur.start); + _outlineCubicTo(*dash.outline, &cur.ctrl1, &cur.ctrl2, &cur.end); + } + if (dash.curLen < 1) { + //move to next dash + dash.curIdx = (dash.curIdx + 1) % dash.cnt; + dash.curLen = dash.pattern[dash.curIdx]; + dash.curOpGap = !dash.curOpGap; + } + } + dash.ptCur = *to; +} + + +SwOutline* _genDashOutline(const Shape& shape) { - /* OPTIMIZE ME: We may avoid this bounding box calculation in this stage - if this shape has stroke and stroke bbox can be used here... */ - if (!_updateBBox(sdata.outline, sdata.bbox)) goto end; - if (!_checkValid(sdata, clip)) goto end; + const PathCommand* cmds = nullptr; + auto cmdCnt = shape.pathCommands(&cmds); + + const Point* pts = nullptr; + auto ptsCnt = shape.pathCoords(&pts); + + //No actual shape data + if (cmdCnt == 0 || ptsCnt == 0) return nullptr; + + SwDashStroke dash; + dash.curIdx = 0; + dash.curLen = 0; + dash.ptStart = {0, 0}; + dash.ptCur = {0, 0}; + dash.curOpGap = false; + + const float* pattern; + dash.cnt = shape.strokeDash(&pattern); + assert(dash.cnt > 0 && pattern); + + //Is it safe to mutual exclusive? + dash.pattern = const_cast(pattern); + dash.outline = static_cast(calloc(1, sizeof(SwOutline))); + assert(dash.outline); + dash.outline->opened = true; + + //smart reservation + auto outlinePtsCnt = 0; + auto outlineCntrsCnt = 0; + + for (uint32_t i = 0; i < cmdCnt; ++i) { + switch(*(cmds + i)) { + case PathCommand::Close: { + ++outlinePtsCnt; + break; + } + case PathCommand::MoveTo: { + ++outlineCntrsCnt; + ++outlinePtsCnt; + break; + } + case PathCommand::LineTo: { + ++outlinePtsCnt; + break; + } + case PathCommand::CubicTo: { + outlinePtsCnt += 3; + break; + } + } + } - sdata.rle = rleRender(sdata.outline, sdata.bbox, clip); + ++outlinePtsCnt; //for close + ++outlineCntrsCnt; //for end + + //Reserve Approximitely 20x... + _growOutlinePoint(*dash.outline, outlinePtsCnt * 20); + _growOutlineContour(*dash.outline, outlineCntrsCnt * 20); + while (cmdCnt-- > 0) { + switch(*cmds) { + case PathCommand::Close: { + _dashLineTo(dash, &dash.ptStart); + break; + } + case PathCommand::MoveTo: { + //reset the dash + dash.curIdx = 0; + dash.curLen = *dash.pattern; + dash.curOpGap = false; + dash.ptStart = dash.ptCur = *pts; + ++pts; + break; + } + case PathCommand::LineTo: { + _dashLineTo(dash, pts); + ++pts; + break; + } + case PathCommand::CubicTo: { + _dashCubicTo(dash, pts, pts + 1, pts + 2); + pts += 3; + break; + } + } + ++cmds; + } + + _outlineEnd(*dash.outline); + + return dash.outline; +} + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +bool shapeGenRle(SwShape& shape, const Shape& sdata, const SwSize& clip, const RenderTransform* transform) +{ + if (!shapeGenOutline(shape, sdata)) return false; + + _transformOutline(shape.outline, transform); + + if (!_updateBBox(shape.outline, shape.bbox)) goto end; + + if (!_checkValid(shape.outline, shape.bbox, clip)) goto end; + + shape.rle = rleRender(shape.outline, shape.bbox, clip); end: - if (sdata.rle) return true; + if (shape.rle) return true; return false; } -void shapeDelOutline(SwShape& sdata) +void shapeDelOutline(SwShape& shape) { - auto outline = sdata.outline; - _freeOutline(outline); - sdata.outline = nullptr; + auto outline = shape.outline; + _delOutline(outline); + shape.outline = nullptr; } -void shapeReset(SwShape& sdata) +void shapeReset(SwShape& shape) { - shapeDelOutline(sdata); - rleFree(sdata.rle); - sdata.rle = nullptr; - _initBBox(sdata.bbox); + shapeDelOutline(shape); + rleFree(shape.rle); + shape.rle = nullptr; + _initBBox(shape.bbox); } -bool shapeGenOutline(const Shape& shape, SwShape& sdata) +bool shapeGenOutline(SwShape& shape, const Shape& sdata) { const PathCommand* cmds = nullptr; - auto cmdCnt = shape.pathCommands(&cmds); + auto cmdCnt = sdata.pathCommands(&cmds); const Point* pts = nullptr; - auto ptsCnt = shape.pathCoords(&pts); + auto ptsCnt = sdata.pathCoords(&pts); //No actual shape data if (cmdCnt == 0 || ptsCnt == 0) return false; @@ -315,7 +621,7 @@ bool shapeGenOutline(const Shape& shape, SwShape& sdata) ++outlinePtsCnt; //for close ++outlineCntrsCnt; //for end - auto outline = sdata.outline; + auto outline = shape.outline; if (!outline) outline = static_cast(calloc(1, sizeof(SwOutline))); assert(outline); outline->opened = true; @@ -354,7 +660,7 @@ bool shapeGenOutline(const Shape& shape, SwShape& sdata) //FIXME: //outline->flags = SwOutline::FillRule::Winding; - sdata.outline = outline; + shape.outline = outline; return true; } @@ -376,38 +682,52 @@ void shapeFree(SwShape* sdata) } -void shapeResetStroke(const Shape& shape, SwShape& sdata) +void shapeResetStroke(SwShape& shape, const Shape& sdata) { - if (!sdata.stroke) sdata.stroke = static_cast(calloc(1, sizeof(SwStroke))); - auto stroke = sdata.stroke; + if (!shape.stroke) shape.stroke = static_cast(calloc(1, sizeof(SwStroke))); + auto stroke = shape.stroke; assert(stroke); - strokeReset(*stroke, shape.strokeWidth(), shape.strokeCap(), shape.strokeJoin()); - rleFree(sdata.strokeRle); - sdata.strokeRle = nullptr; + + strokeReset(*stroke, sdata); + + rleFree(shape.strokeRle); + shape.strokeRle = nullptr; } -bool shapeGenStrokeRle(const Shape& shape, SwShape& sdata, const SwSize& clip) +bool shapeGenStrokeRle(SwShape& shape, const Shape& sdata, const SwSize& clip) { - if (!sdata.outline) { - if (!shapeGenOutline(shape, sdata)) return false; - } + SwOutline* shapeOutline = nullptr; - if (!_checkValid(sdata, clip)) return false; + //Dash Style Stroke + if (sdata.strokeDash(nullptr) > 0) { + shapeOutline = _genDashOutline(sdata); + if (!shapeOutline) return false; + + //Normal Style stroke + } else { + if (!shape.outline) { + if (!shapeGenOutline(shape, sdata)) return false; + } + shapeOutline = shape.outline; + } - if (!strokeParseOutline(*sdata.stroke, *sdata.outline)) return false; + if (!strokeParseOutline(*shape.stroke, *shapeOutline)) return false; - auto outline = strokeExportOutline(*sdata.stroke); - if (!outline) return false; + auto strokeOutline = strokeExportOutline(*shape.stroke); + if (!strokeOutline) return false; SwBBox bbox; - _updateBBox(outline, bbox); + _updateBBox(strokeOutline, bbox); - sdata.strokeRle = rleRender(outline, bbox, clip); + if (!_checkValid(strokeOutline, bbox, clip)) return false; - _freeOutline(outline); + shape.strokeRle = rleRender(strokeOutline, bbox, clip); + + _delOutline(strokeOutline); return true; } + #endif /* _TVG_SW_SHAPE_H_ */ diff --git a/src/lib/sw_engine/tvgSwStroke.cpp b/src/lib/sw_engine/tvgSwStroke.cpp index 06ec989..fdc8cae 100644 --- a/src/lib/sw_engine/tvgSwStroke.cpp +++ b/src/lib/sw_engine/tvgSwStroke.cpp @@ -351,7 +351,7 @@ void _processCorner(SwStroke& stroke, SwFixed lineLength) } -void _subPathStart(SwStroke& stroke, SwFixed startAngle, SwFixed lineLength) +void _firstSubPath(SwStroke& stroke, SwFixed startAngle, SwFixed lineLength) { SwPoint delta = {stroke.width, 0}; mathRotate(delta, startAngle + ANGLE_PI2); @@ -390,7 +390,7 @@ static void _lineTo(SwStroke& stroke, const SwPoint& to) if (stroke.firstPt) { /* This is the first segment of a subpath. We need to add a point to each border at their respective starting point locations. */ - _subPathStart(stroke, angle, lineLength); + _firstSubPath(stroke, angle, lineLength); } else { //process the current corner stroke.angleOut = angle; @@ -455,7 +455,7 @@ static void _cubicTo(SwStroke& stroke, const SwPoint& ctrl1, const SwPoint& ctrl firstArc = false; //process corner if necessary if (stroke.firstPt) { - _subPathStart(stroke, angleIn, 0); + _firstSubPath(stroke, angleIn, 0); } else { stroke.angleOut = angleIn; _processCorner(stroke, 0); @@ -566,7 +566,6 @@ static void _addCap(SwStroke& stroke, SwFixed angle, int32_t side) SwPoint delta2 = {stroke.width, 0}; mathRotate(delta2, angle + rotate); - delta += stroke.center + delta2; _borderLineTo(border, delta, false); @@ -660,26 +659,26 @@ static void _beginSubPath(SwStroke& stroke, SwPoint& to, bool opened) stroke.firstPt = true; stroke.center = to; - stroke.subPathOpen = opened; + stroke.openSubPath = opened; /* Determine if we need to check whether the border radius is greater than the radius of curvature of a curve, to handle this case specially. This is only required if bevel joins or butt caps may be created because round & miter joins and round & square caps cover the nagative sector created with wide strokes. */ - if ((stroke.join != StrokeJoin::Round) || (stroke.subPathOpen && stroke.cap == StrokeCap::Butt)) + if ((stroke.join != StrokeJoin::Round) || (stroke.openSubPath && stroke.cap == StrokeCap::Butt)) stroke.handleWideStrokes = true; else stroke.handleWideStrokes = false; - stroke.subPathStart = to; + stroke.ptStartSubPath = to; stroke.angleIn = 0; } static void _endSubPath(SwStroke& stroke) { - if (stroke.subPathOpen) { + if (stroke.openSubPath) { auto right = stroke.borders; assert(right); @@ -692,7 +691,7 @@ static void _endSubPath(SwStroke& stroke) _addReverseLeft(stroke, true); //now add the final cap - stroke.center = stroke.subPathStart; + stroke.center = stroke.ptStartSubPath; _addCap(stroke, stroke.subPathAngle + ANGLE_PI, 0); /* now end the right subpath accordingly. The left one is rewind @@ -701,8 +700,8 @@ static void _endSubPath(SwStroke& stroke) } else { //close the path if needed - if (stroke.center != stroke.subPathStart) - _lineTo(stroke, stroke.subPathStart); + if (stroke.center != stroke.ptStartSubPath) + _lineTo(stroke, stroke.ptStartSubPath); //process the corner stroke.angleOut = stroke.subPathAngle; @@ -792,7 +791,6 @@ static void _exportBorderOutline(SwStroke& stroke, SwOutline* outline, uint32_t ++cntrs; ++outline->cntrsCnt; } - ++src; ++tags; ++idx; @@ -820,13 +818,13 @@ void strokeFree(SwStroke* stroke) } -void strokeReset(SwStroke& stroke, float width, StrokeCap cap, StrokeJoin join) +void strokeReset(SwStroke& stroke, const Shape& shape) { - stroke.width = TO_SWCOORD(width * 0.5); - stroke.cap = cap; + stroke.width = TO_SWCOORD(shape.strokeWidth() * 0.5); + stroke.cap = shape.strokeCap(); //Save line join: it can be temporarily changed when stroking curves... - stroke.joinSaved = stroke.join = join; + stroke.joinSaved = stroke.join = shape.strokeJoin(); stroke.borders[0].ptsCnt = 0; stroke.borders[0].start = -1; @@ -838,7 +836,7 @@ void strokeReset(SwStroke& stroke, float width, StrokeCap cap, StrokeJoin join) } -bool strokeParseOutline(SwStroke& stroke, SwOutline& outline) +bool strokeParseOutline(SwStroke& stroke, const SwOutline& outline) { uint32_t first = 0; @@ -926,5 +924,4 @@ SwOutline* strokeExportOutline(SwStroke& stroke) return outline; } - #endif /* _TVG_SW_STROKER_H_ */ diff --git a/src/lib/tvgShape.cpp b/src/lib/tvgShape.cpp index 828e251..67d5ab7 100644 --- a/src/lib/tvgShape.cpp +++ b/src/lib/tvgShape.cpp @@ -328,7 +328,7 @@ int Shape::strokeColor(uint8_t* r, uint8_t* g, uint8_t* b, uint8_t* a) const noe } -int Shape::stroke(const size_t* dashPattern, size_t cnt) noexcept +int Shape::stroke(const float* dashPattern, size_t cnt) noexcept { if (cnt < 2 || !dashPattern) return -1; @@ -341,7 +341,7 @@ int Shape::stroke(const size_t* dashPattern, size_t cnt) noexcept } -size_t Shape::strokeDash(const size_t** dashPattern) const noexcept +size_t Shape::strokeDash(const float** dashPattern) const noexcept { auto impl = pImpl.get(); assert(impl); diff --git a/src/lib/tvgShapeImpl.h b/src/lib/tvgShapeImpl.h index e1a4e9f..09956be 100644 --- a/src/lib/tvgShapeImpl.h +++ b/src/lib/tvgShapeImpl.h @@ -32,7 +32,7 @@ struct ShapeStroke { float width = 0; uint8_t color[4] = {0, 0, 0, 0}; - size_t* dashPattern = nullptr; + float* dashPattern = nullptr; size_t dashCnt = 0; StrokeCap cap = StrokeCap::Square; StrokeJoin join = StrokeJoin::Bevel; @@ -203,7 +203,7 @@ struct Shape::Impl return 0; } - bool strokeDash(const size_t* pattern, size_t cnt) + bool strokeDash(const float* pattern, size_t cnt) { assert(pattern); @@ -215,12 +215,13 @@ struct Shape::Impl stroke->dashPattern = nullptr; } - if (!stroke->dashPattern) stroke->dashPattern = static_cast(malloc(sizeof(size_t) * cnt)); + if (!stroke->dashPattern) stroke->dashPattern = static_cast(malloc(sizeof(float) * cnt)); assert(stroke->dashPattern); - memcpy(stroke->dashPattern, pattern, cnt); - stroke->dashCnt = cnt; + for (size_t i = 0; i < cnt; ++i) + stroke->dashPattern[i] = pattern[i]; + stroke->dashCnt = cnt; flag |= RenderUpdateFlag::Stroke; return 0; diff --git a/test/testStrokeLine.cpp b/test/testStrokeLine.cpp index 8687b85..1288c07 100644 --- a/test/testStrokeLine.cpp +++ b/test/testStrokeLine.cpp @@ -17,6 +17,7 @@ void tvgtest() auto canvas = tvg::SwCanvas::gen(); canvas->target(buffer, WIDTH, WIDTH, HEIGHT); + //Test for Stroke Width for (int i = 0; i < 10; ++i) { auto shape = tvg::Shape::gen(); shape->moveTo(50, 50 + (25 * i)); @@ -27,6 +28,8 @@ void tvgtest() canvas->push(move(shape)); } + + //Test for StrokeJoin & StrokeCap auto shape1 = tvg::Shape::gen(); shape1->moveTo(20, 350); shape1->lineTo(250, 350); @@ -63,6 +66,52 @@ void tvgtest() shape3->stroke(tvg::StrokeCap::Butt); canvas->push(move(shape3)); + //Test for Stroke Dash + auto shape4 = tvg::Shape::gen(); + shape4->moveTo(20, 600); + shape4->lineTo(250, 600); + shape4->lineTo(220, 750); + shape4->lineTo(70, 720); + shape4->lineTo(70, 580); + shape4->stroke(255, 0, 0, 255); + shape4->stroke(5); + shape4->stroke(tvg::StrokeJoin::Round); + shape4->stroke(tvg::StrokeCap::Round); + + float dashPattern1[2] = {10, 10}; + shape4->stroke(dashPattern1, 2); + canvas->push(move(shape4)); + + auto shape5 = tvg::Shape::gen(); + shape5->moveTo(270, 600); + shape5->lineTo(500, 600); + shape5->lineTo(470, 750); + shape5->lineTo(320, 720); + shape5->lineTo(320, 580); + shape5->stroke(255, 255, 0, 255); + shape5->stroke(5); + shape5->stroke(tvg::StrokeJoin::Bevel); + shape5->stroke(tvg::StrokeCap::Butt); + + float dashPattern2[4] = {10, 10}; + shape5->stroke(dashPattern2, 4); + canvas->push(move(shape5)); + + auto shape6 = tvg::Shape::gen(); + shape6->moveTo(520, 600); + shape6->lineTo(750, 600); + shape6->lineTo(720, 750); + shape6->lineTo(570, 720); + shape6->lineTo(570, 580); + shape6->stroke(255, 255, 255, 255); + shape6->stroke(5); + shape6->stroke(tvg::StrokeJoin::Miter); + shape6->stroke(tvg::StrokeCap::Square); + + float dashPattern3[2] = {10, 10}; + shape6->stroke(dashPattern3, 2); + canvas->push(move(shape6)); + canvas->draw(); canvas->sync(); -- 2.7.4