sw_engine: support stroke dash feature

Change-Id: Ibed8bcb6a07952a059bb9a7355f7c43db97aa672
This commit is contained in:
Hermet Park 2020-06-03 20:50:13 +09:00
parent dc5f9f7430
commit 9aa2566b45
9 changed files with 537 additions and 164 deletions

View file

@ -143,7 +143,7 @@ public:
//Stroke //Stroke
int stroke(float width) noexcept; int stroke(float width) noexcept;
int stroke(uint8_t r, uint8_t g, uint8_t b, uint8_t a) 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(StrokeCap cap) noexcept;
int stroke(StrokeJoin join) noexcept; int stroke(StrokeJoin join) noexcept;
@ -163,7 +163,7 @@ public:
float strokeWidth() const noexcept; float strokeWidth() const noexcept;
int strokeColor(uint8_t* r, uint8_t* g, uint8_t* b, uint8_t* a) 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; StrokeCap strokeCap() const noexcept;
StrokeJoin strokeJoin() const noexcept; StrokeJoin strokeJoin() const noexcept;

View file

@ -125,7 +125,8 @@ struct SwStroke
SwFixed angleOut; SwFixed angleOut;
SwPoint center; SwPoint center;
SwFixed lineLength; SwFixed lineLength;
SwPoint subPathStart; SwFixed subPathAngle;
SwPoint ptStartSubPath;
SwFixed subPathLineLength; SwFixed subPathLineLength;
SwFixed width; SwFixed width;
@ -136,11 +137,22 @@ struct SwStroke
SwStrokeBorder borders[2]; SwStrokeBorder borders[2];
bool firstPt; bool firstPt;
bool subPathOpen; bool openSubPath;
bool subPathAngle;
bool handleWideStrokes; 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 struct SwShape
{ {
SwOutline* outline; SwOutline* outline;
@ -183,18 +195,16 @@ SwFixed mathLength(SwPoint& pt);
bool mathSmallCubic(SwPoint* base, SwFixed& angleIn, SwFixed& angleMid, SwFixed& angleOut); bool mathSmallCubic(SwPoint* base, SwFixed& angleIn, SwFixed& angleMid, SwFixed& angleOut);
SwFixed mathMean(SwFixed angle1, SwFixed angle2); SwFixed mathMean(SwFixed angle1, SwFixed angle2);
void shapeReset(SwShape& sdata); void shapeReset(SwShape& shape);
bool shapeGenOutline(const Shape& shape, SwShape& sdata); bool shapeGenOutline(SwShape& shape, const Shape& sdata);
bool shapeGenRle(const Shape& shape, SwShape& sdata, const SwSize& clip); bool shapeGenRle(SwShape& shape, const Shape& sdata, const SwSize& clip, const RenderTransform* transform);
void shapeDelOutline(SwShape& sdata); void shapeDelOutline(SwShape& shape);
void shapeResetStroke(const Shape& shape, SwShape& sdata); void shapeResetStroke(SwShape& shape, const Shape& sdata);
bool shapeGenStrokeOutline(const Shape& shape, SwShape& sdata); bool shapeGenStrokeRle(SwShape& shape, const Shape& sdata, const SwSize& clip);
bool shapeGenStrokeRle(const Shape& shape, SwShape& sdata, const SwSize& clip); void shapeFree(SwShape* shape);
void shapeTransformOutline(const Shape& shape, SwShape& sdata, const RenderTransform& transform);
void shapeFree(SwShape* sdata);
void strokeReset(SwStroke& stroke, float width, StrokeCap cap, StrokeJoin join); void strokeReset(SwStroke& stroke, const Shape& shape);
bool strokeParseOutline(SwStroke& stroke, SwOutline& outline); bool strokeParseOutline(SwStroke& stroke, const SwOutline& outline);
SwOutline* strokeExportOutline(SwStroke& stroke); SwOutline* strokeExportOutline(SwStroke& stroke);
void strokeFree(SwStroke* stroke); void strokeFree(SwStroke* stroke);

View file

@ -140,20 +140,21 @@ static void _polarize(SwPoint& pt)
static void _rotate(SwPoint& pt, SwFixed theta) 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 //Rotate inside [-PI/4, PI/4] sector
while (theta < -ANGLE_PI4) { while (theta < -ANGLE_PI4) {
auto tmp = v.y; auto tmp = y;
v.y = -v.x; y = -x;
v.x = tmp; x = tmp;
theta += ANGLE_PI2; theta += ANGLE_PI2;
} }
while (theta > ANGLE_PI4) { while (theta > ANGLE_PI4) {
auto tmp = -v.y; auto tmp = -y;
v.y = v.x; y = x;
v.x = tmp; x = tmp;
theta -= ANGLE_PI2; 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) { for (i = 1, j = 1; i < ATAN_MAX; j <<= 1, ++i) {
if (theta < 0) { if (theta < 0) {
auto tmp = v.x + ((v.y + j) >> i); auto tmp = x + ((y + j) >> i);
v.y = v.y - ((v.x + j) >> i); y = y - ((x + j) >> i);
v.x = tmp; x = tmp;
theta += *atan++; theta += *atan++;
}else { }else {
auto tmp = v.x - ((v.y + j) >> i); auto tmp = x - ((y + j) >> i);
v.y = v.y + ((v.x + j) >> i); y = y + ((x + j) >> i);
v.x = tmp; x = tmp;
theta -= *atan++; theta -= *atan++;
} }
} }
pt = v; pt = {x, y};
} }
@ -305,8 +306,8 @@ void mathRotate(SwPoint& pt, SwFixed angle)
auto v = pt; auto v = pt;
auto shift = _normalize(v); auto shift = _normalize(v);
auto theta = angle;
auto theta = angle;
_rotate(v, theta); _rotate(v, theta);
v.x = _downscale(v.x); v.x = _downscale(v.x);

View file

@ -82,16 +82,16 @@ bool SwRenderer::dispose(const Shape& shape, void *data)
return true; 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 //prepare shape data
SwShape* sdata = static_cast<SwShape*>(data); auto shape = static_cast<SwShape*>(data);
if (!sdata) { if (!shape) {
sdata = static_cast<SwShape*>(calloc(1, sizeof(SwShape))); shape = static_cast<SwShape*>(calloc(1, sizeof(SwShape)));
assert(sdata); assert(shape);
} }
if (flags == RenderUpdateFlag::None) return sdata; if (flags == RenderUpdateFlag::None) return shape;
//TODO: Threading //TODO: Threading
@ -99,34 +99,29 @@ void* SwRenderer::prepare(const Shape& shape, void* data, const RenderTransform*
//Shape //Shape
if (flags & (RenderUpdateFlag::Path | RenderUpdateFlag::Transform)) { if (flags & (RenderUpdateFlag::Path | RenderUpdateFlag::Transform)) {
shapeReset(*shape);
uint8_t alpha = 0; uint8_t alpha = 0;
shape.fill(nullptr, nullptr, nullptr, &alpha); sdata.fill(nullptr, nullptr, nullptr, &alpha);
if (alpha > 0) { if (alpha > 0) {
shapeReset(*sdata); if (!shapeGenRle(*shape, sdata, clip, transform)) return shape;
if (!shapeGenOutline(shape, *sdata)) return sdata;
if (transform) shapeTransformOutline(shape, *sdata, *transform);
if (!shapeGenRle(shape, *sdata, clip)) return sdata;
} }
} }
//Stroke //Stroke
if (flags & (RenderUpdateFlag::Stroke | RenderUpdateFlag::Transform)) { if (flags & (RenderUpdateFlag::Stroke | RenderUpdateFlag::Transform)) {
if (sdata.strokeWidth() > 0.5) {
if (shape.strokeWidth() > 0.5) { shapeResetStroke(*shape, sdata);
shapeResetStroke(shape, *sdata);
uint8_t alpha = 0; uint8_t alpha = 0;
shape.strokeColor(nullptr, nullptr, nullptr, &alpha); sdata.strokeColor(nullptr, nullptr, nullptr, &alpha);
if (alpha > 0) { 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;
} }

View file

@ -23,48 +23,164 @@
/* Internal Class Implementation */ /* Internal Class Implementation */
/************************************************************************/ /************************************************************************/
struct Line
{
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);
}
return len;
}
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 float _bezAt(const Bezier& bz, float at)
{
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;
}
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) static void _growOutlineContour(SwOutline& outline, uint32_t n)
{ {
if (n == 0) {
free(outline.cntrs);
outline.cntrs = nullptr;
outline.cntrsCnt = 0;
outline.reservedCntrsCnt = 0;
return;
}
if (outline.reservedCntrsCnt >= outline.cntrsCnt + n) return; if (outline.reservedCntrsCnt >= outline.cntrsCnt + n) return;
outline.reservedCntrsCnt = outline.cntrsCnt + n;
//cout << "Grow Cntrs: " << outline.reservedCntrsCnt << " -> " << outline.cntrsCnt + n << endl;; outline.cntrs = static_cast<uint32_t*>(realloc(outline.cntrs, outline.reservedCntrsCnt * sizeof(uint32_t)));
outline.reservedCntrsCnt = n;
outline.cntrs = static_cast<uint32_t*>(realloc(outline.cntrs, n * sizeof(uint32_t)));
assert(outline.cntrs); assert(outline.cntrs);
} }
static void _growOutlinePoint(SwOutline& outline, uint32_t n) static void _growOutlinePoint(SwOutline& outline, uint32_t n)
{ {
if (n == 0) {
free(outline.pts);
outline.pts = nullptr;
free(outline.types);
outline.types = nullptr;
outline.reservedPtsCnt = 0;
outline.ptsCnt = 0;
return;
}
if (outline.reservedPtsCnt >= outline.ptsCnt + n) return; if (outline.reservedPtsCnt >= outline.ptsCnt + n) return;
outline.reservedPtsCnt = outline.ptsCnt + n;
//cout << "Grow Pts: " << outline.reservedPtsCnt << " -> " << outline.ptsCnt + n << endl; outline.pts = static_cast<SwPoint*>(realloc(outline.pts, outline.reservedPtsCnt * sizeof(SwPoint)));
outline.reservedPtsCnt = n;
outline.pts = static_cast<SwPoint*>(realloc(outline.pts, n * sizeof(SwPoint)));
assert(outline.pts); assert(outline.pts);
outline.types = static_cast<uint8_t*>(realloc(outline.types, n * sizeof(uint8_t))); outline.types = static_cast<uint8_t*>(realloc(outline.types, outline.reservedPtsCnt * sizeof(uint8_t)));
assert(outline.types); assert(outline.types);
} }
static void _freeOutline(SwOutline* outline) static void _delOutline(SwOutline* outline)
{ {
if (!outline) return; if (!outline) return;
@ -112,7 +228,6 @@ static void _outlineLineTo(SwOutline& outline, const Point* to)
outline.pts[outline.ptsCnt] = TO_SWPOINT(to); outline.pts[outline.ptsCnt] = TO_SWPOINT(to);
outline.types[outline.ptsCnt] = SW_CURVE_TYPE_POINT; outline.types[outline.ptsCnt] = SW_CURVE_TYPE_POINT;
++outline.ptsCnt; ++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 //Check boundary
if ((sdata.bbox.min.x > clip.w || sdata.bbox.min.y > clip.h) || if ((bbox.min.x > clip.w || bbox.min.y > clip.h) ||
(sdata.bbox.min.x + sdata.bbox.max.x < 0) || (bbox.min.x + bbox.max.x < 0) ||
(sdata.bbox.min.y + sdata.bbox.max.y < 0)) return false; (bbox.min.y + bbox.max.y < 0)) return false;
return true; return true;
} }
static void _transformOutline(SwOutline* outline, const RenderTransform* transform)
{
assert(outline);
if (!transform) return;
for(uint32_t i = 0; i < outline->ptsCnt; ++i) {
auto dx = static_cast<float>(outline->pts[i].x >> 6);
auto dy = static_cast<float>(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};
outline->pts[i] = TO_SWPOINT(&pt);
}
}
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)
{
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<float*>(pattern);
dash.outline = static_cast<SwOutline*>(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;
}
}
}
++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 */ /* External Class Implementation */
/************************************************************************/ /************************************************************************/
void shapeTransformOutline(const Shape& shape, SwShape& sdata, const RenderTransform& transform) bool shapeGenRle(SwShape& shape, const Shape& sdata, const SwSize& clip, const RenderTransform* transform)
{ {
auto outline = sdata.outline; if (!shapeGenOutline(shape, sdata)) return false;
assert(outline);
for(uint32_t i = 0; i < outline->ptsCnt; ++i) { _transformOutline(shape.outline, transform);
auto dx = static_cast<float>(outline->pts[i].x >> 6);
auto dy = static_cast<float>(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};
outline->pts[i] = TO_SWPOINT(&pt);
}
}
if (!_updateBBox(shape.outline, shape.bbox)) goto end;
bool shapeGenRle(const Shape& shape, SwShape& sdata, const SwSize& clip) if (!_checkValid(shape.outline, shape.bbox, clip)) goto end;
{
/* 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;
sdata.rle = rleRender(sdata.outline, sdata.bbox, clip);
shape.rle = rleRender(shape.outline, shape.bbox, clip);
end: end:
if (sdata.rle) return true; if (shape.rle) return true;
return false; return false;
} }
void shapeDelOutline(SwShape& sdata) void shapeDelOutline(SwShape& shape)
{ {
auto outline = sdata.outline; auto outline = shape.outline;
_freeOutline(outline); _delOutline(outline);
sdata.outline = nullptr; shape.outline = nullptr;
} }
void shapeReset(SwShape& sdata) void shapeReset(SwShape& shape)
{ {
shapeDelOutline(sdata); shapeDelOutline(shape);
rleFree(sdata.rle); rleFree(shape.rle);
sdata.rle = nullptr; shape.rle = nullptr;
_initBBox(sdata.bbox); _initBBox(shape.bbox);
} }
bool shapeGenOutline(const Shape& shape, SwShape& sdata) bool shapeGenOutline(SwShape& shape, const Shape& sdata)
{ {
const PathCommand* cmds = nullptr; const PathCommand* cmds = nullptr;
auto cmdCnt = shape.pathCommands(&cmds); auto cmdCnt = sdata.pathCommands(&cmds);
const Point* pts = nullptr; const Point* pts = nullptr;
auto ptsCnt = shape.pathCoords(&pts); auto ptsCnt = sdata.pathCoords(&pts);
//No actual shape data //No actual shape data
if (cmdCnt == 0 || ptsCnt == 0) return false; if (cmdCnt == 0 || ptsCnt == 0) return false;
@ -315,7 +621,7 @@ bool shapeGenOutline(const Shape& shape, SwShape& sdata)
++outlinePtsCnt; //for close ++outlinePtsCnt; //for close
++outlineCntrsCnt; //for end ++outlineCntrsCnt; //for end
auto outline = sdata.outline; auto outline = shape.outline;
if (!outline) outline = static_cast<SwOutline*>(calloc(1, sizeof(SwOutline))); if (!outline) outline = static_cast<SwOutline*>(calloc(1, sizeof(SwOutline)));
assert(outline); assert(outline);
outline->opened = true; outline->opened = true;
@ -354,7 +660,7 @@ bool shapeGenOutline(const Shape& shape, SwShape& sdata)
//FIXME: //FIXME:
//outline->flags = SwOutline::FillRule::Winding; //outline->flags = SwOutline::FillRule::Winding;
sdata.outline = outline; shape.outline = outline;
return true; 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<SwStroke*>(calloc(1, sizeof(SwStroke))); if (!shape.stroke) shape.stroke = static_cast<SwStroke*>(calloc(1, sizeof(SwStroke)));
auto stroke = sdata.stroke; auto stroke = shape.stroke;
assert(stroke); assert(stroke);
strokeReset(*stroke, shape.strokeWidth(), shape.strokeCap(), shape.strokeJoin());
rleFree(sdata.strokeRle); strokeReset(*stroke, sdata);
sdata.strokeRle = nullptr;
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) { SwOutline* shapeOutline = nullptr;
//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; if (!shapeGenOutline(shape, sdata)) return false;
} }
shapeOutline = shape.outline;
}
if (!_checkValid(sdata, clip)) return false; if (!strokeParseOutline(*shape.stroke, *shapeOutline)) return false;
if (!strokeParseOutline(*sdata.stroke, *sdata.outline)) return false; auto strokeOutline = strokeExportOutline(*shape.stroke);
if (!strokeOutline) return false;
auto outline = strokeExportOutline(*sdata.stroke);
if (!outline) return false;
SwBBox bbox; 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; return true;
} }
#endif /* _TVG_SW_SHAPE_H_ */ #endif /* _TVG_SW_SHAPE_H_ */

View file

@ -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}; SwPoint delta = {stroke.width, 0};
mathRotate(delta, startAngle + ANGLE_PI2); mathRotate(delta, startAngle + ANGLE_PI2);
@ -390,7 +390,7 @@ static void _lineTo(SwStroke& stroke, const SwPoint& to)
if (stroke.firstPt) { if (stroke.firstPt) {
/* This is the first segment of a subpath. We need to add a point to each border /* This is the first segment of a subpath. We need to add a point to each border
at their respective starting point locations. */ at their respective starting point locations. */
_subPathStart(stroke, angle, lineLength); _firstSubPath(stroke, angle, lineLength);
} else { } else {
//process the current corner //process the current corner
stroke.angleOut = angle; stroke.angleOut = angle;
@ -455,7 +455,7 @@ static void _cubicTo(SwStroke& stroke, const SwPoint& ctrl1, const SwPoint& ctrl
firstArc = false; firstArc = false;
//process corner if necessary //process corner if necessary
if (stroke.firstPt) { if (stroke.firstPt) {
_subPathStart(stroke, angleIn, 0); _firstSubPath(stroke, angleIn, 0);
} else { } else {
stroke.angleOut = angleIn; stroke.angleOut = angleIn;
_processCorner(stroke, 0); _processCorner(stroke, 0);
@ -566,7 +566,6 @@ static void _addCap(SwStroke& stroke, SwFixed angle, int32_t side)
SwPoint delta2 = {stroke.width, 0}; SwPoint delta2 = {stroke.width, 0};
mathRotate(delta2, angle + rotate); mathRotate(delta2, angle + rotate);
delta += stroke.center + delta2; delta += stroke.center + delta2;
_borderLineTo(border, delta, false); _borderLineTo(border, delta, false);
@ -660,26 +659,26 @@ static void _beginSubPath(SwStroke& stroke, SwPoint& to, bool opened)
stroke.firstPt = true; stroke.firstPt = true;
stroke.center = to; stroke.center = to;
stroke.subPathOpen = opened; stroke.openSubPath = opened;
/* Determine if we need to check whether the border radius is greater /* 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. 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 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 round & miter joins and round & square caps cover the nagative sector
created with wide strokes. */ 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; stroke.handleWideStrokes = true;
else else
stroke.handleWideStrokes = false; stroke.handleWideStrokes = false;
stroke.subPathStart = to; stroke.ptStartSubPath = to;
stroke.angleIn = 0; stroke.angleIn = 0;
} }
static void _endSubPath(SwStroke& stroke) static void _endSubPath(SwStroke& stroke)
{ {
if (stroke.subPathOpen) { if (stroke.openSubPath) {
auto right = stroke.borders; auto right = stroke.borders;
assert(right); assert(right);
@ -692,7 +691,7 @@ static void _endSubPath(SwStroke& stroke)
_addReverseLeft(stroke, true); _addReverseLeft(stroke, true);
//now add the final cap //now add the final cap
stroke.center = stroke.subPathStart; stroke.center = stroke.ptStartSubPath;
_addCap(stroke, stroke.subPathAngle + ANGLE_PI, 0); _addCap(stroke, stroke.subPathAngle + ANGLE_PI, 0);
/* now end the right subpath accordingly. The left one is rewind /* now end the right subpath accordingly. The left one is rewind
@ -701,8 +700,8 @@ static void _endSubPath(SwStroke& stroke)
} else { } else {
//close the path if needed //close the path if needed
if (stroke.center != stroke.subPathStart) if (stroke.center != stroke.ptStartSubPath)
_lineTo(stroke, stroke.subPathStart); _lineTo(stroke, stroke.ptStartSubPath);
//process the corner //process the corner
stroke.angleOut = stroke.subPathAngle; stroke.angleOut = stroke.subPathAngle;
@ -792,7 +791,6 @@ static void _exportBorderOutline(SwStroke& stroke, SwOutline* outline, uint32_t
++cntrs; ++cntrs;
++outline->cntrsCnt; ++outline->cntrsCnt;
} }
++src; ++src;
++tags; ++tags;
++idx; ++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.width = TO_SWCOORD(shape.strokeWidth() * 0.5);
stroke.cap = cap; stroke.cap = shape.strokeCap();
//Save line join: it can be temporarily changed when stroking curves... //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].ptsCnt = 0;
stroke.borders[0].start = -1; 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; uint32_t first = 0;
@ -926,5 +924,4 @@ SwOutline* strokeExportOutline(SwStroke& stroke)
return outline; return outline;
} }
#endif /* _TVG_SW_STROKER_H_ */ #endif /* _TVG_SW_STROKER_H_ */

View file

@ -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; 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(); auto impl = pImpl.get();
assert(impl); assert(impl);

View file

@ -32,7 +32,7 @@ struct ShapeStroke
{ {
float width = 0; float width = 0;
uint8_t color[4] = {0, 0, 0, 0}; uint8_t color[4] = {0, 0, 0, 0};
size_t* dashPattern = nullptr; float* dashPattern = nullptr;
size_t dashCnt = 0; size_t dashCnt = 0;
StrokeCap cap = StrokeCap::Square; StrokeCap cap = StrokeCap::Square;
StrokeJoin join = StrokeJoin::Bevel; StrokeJoin join = StrokeJoin::Bevel;
@ -203,7 +203,7 @@ struct Shape::Impl
return 0; return 0;
} }
bool strokeDash(const size_t* pattern, size_t cnt) bool strokeDash(const float* pattern, size_t cnt)
{ {
assert(pattern); assert(pattern);
@ -215,12 +215,13 @@ struct Shape::Impl
stroke->dashPattern = nullptr; stroke->dashPattern = nullptr;
} }
if (!stroke->dashPattern) stroke->dashPattern = static_cast<size_t*>(malloc(sizeof(size_t) * cnt)); if (!stroke->dashPattern) stroke->dashPattern = static_cast<float*>(malloc(sizeof(float) * cnt));
assert(stroke->dashPattern); assert(stroke->dashPattern);
memcpy(stroke->dashPattern, pattern, cnt); for (size_t i = 0; i < cnt; ++i)
stroke->dashCnt = cnt; stroke->dashPattern[i] = pattern[i];
stroke->dashCnt = cnt;
flag |= RenderUpdateFlag::Stroke; flag |= RenderUpdateFlag::Stroke;
return 0; return 0;

View file

@ -17,6 +17,7 @@ void tvgtest()
auto canvas = tvg::SwCanvas::gen(); auto canvas = tvg::SwCanvas::gen();
canvas->target(buffer, WIDTH, WIDTH, HEIGHT); canvas->target(buffer, WIDTH, WIDTH, HEIGHT);
//Test for Stroke Width
for (int i = 0; i < 10; ++i) { for (int i = 0; i < 10; ++i) {
auto shape = tvg::Shape::gen(); auto shape = tvg::Shape::gen();
shape->moveTo(50, 50 + (25 * i)); shape->moveTo(50, 50 + (25 * i));
@ -27,6 +28,8 @@ void tvgtest()
canvas->push(move(shape)); canvas->push(move(shape));
} }
//Test for StrokeJoin & StrokeCap
auto shape1 = tvg::Shape::gen(); auto shape1 = tvg::Shape::gen();
shape1->moveTo(20, 350); shape1->moveTo(20, 350);
shape1->lineTo(250, 350); shape1->lineTo(250, 350);
@ -63,6 +66,52 @@ void tvgtest()
shape3->stroke(tvg::StrokeCap::Butt); shape3->stroke(tvg::StrokeCap::Butt);
canvas->push(move(shape3)); 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->draw();
canvas->sync(); canvas->sync();