Implemented dashed stroke rendering
This commit is contained in:
parent
7efce85328
commit
dc75508682
@ -140,6 +140,9 @@ typedef struct NSVGshape
|
||||
NSVGpaint stroke; // Stroke paint
|
||||
float opacity; // Opacity of the shape.
|
||||
float strokeWidth; // Stroke width (scaled).
|
||||
float strokeDashOffset; // Stroke dash offset (scaled).
|
||||
float strokeDashArray[8]; // Stroke dash array (scaled).
|
||||
char strokeDashCount; // Number of dash values in dash array.
|
||||
char strokeLineJoin; // Stroke join type.
|
||||
char strokeLineCap; // Stroke cap type.
|
||||
char fillRule; // Fill rule, see NSVGfillRule.
|
||||
@ -348,6 +351,8 @@ enum NSVGgradientUnits {
|
||||
NSVG_OBJECT_SPACE = 1,
|
||||
};
|
||||
|
||||
#define NSVG_MAX_DASHES 8
|
||||
|
||||
enum NSVGunits {
|
||||
NSVG_UNITS_USER,
|
||||
NSVG_UNITS_PX,
|
||||
@ -403,6 +408,9 @@ typedef struct NSVGattrib
|
||||
char fillGradient[64];
|
||||
char strokeGradient[64];
|
||||
float strokeWidth;
|
||||
float strokeDashOffset;
|
||||
float strokeDashArray[NSVG_MAX_DASHES];
|
||||
int strokeDashCount;
|
||||
char strokeLineJoin;
|
||||
char strokeLineCap;
|
||||
char fillRule;
|
||||
@ -614,7 +622,6 @@ static NSVGparser* nsvg__createParser()
|
||||
p->attr[0].strokeLineCap = NSVG_CAP_BUTT;
|
||||
p->attr[0].fillRule = NSVG_FILLRULE_NONZERO;
|
||||
p->attr[0].hasFill = 1;
|
||||
p->attr[0].hasStroke = 0;
|
||||
p->attr[0].visible = 1;
|
||||
|
||||
return p;
|
||||
@ -913,6 +920,7 @@ static void nsvg__addShape(NSVGparser* p)
|
||||
float scale = 1.0f;
|
||||
NSVGshape *shape, *cur, *prev;
|
||||
NSVGpath* path;
|
||||
int i;
|
||||
|
||||
if (p->plist == NULL)
|
||||
return;
|
||||
@ -924,6 +932,10 @@ static void nsvg__addShape(NSVGparser* p)
|
||||
memcpy(shape->id, attr->id, sizeof shape->id);
|
||||
scale = nsvg__getAverageScale(attr->xform);
|
||||
shape->strokeWidth = attr->strokeWidth * scale;
|
||||
shape->strokeDashOffset = attr->strokeDashOffset * scale;
|
||||
shape->strokeDashCount = attr->strokeDashCount;
|
||||
for (i = 0; i < attr->strokeDashCount; i++)
|
||||
shape->strokeDashArray[i] = attr->strokeDashArray[i] * scale;
|
||||
shape->strokeLineJoin = attr->strokeLineJoin;
|
||||
shape->strokeLineCap = attr->strokeLineCap;
|
||||
shape->fillRule = attr->fillRule;
|
||||
@ -1574,6 +1586,48 @@ static char nsvg__parseFillRule(const char* str)
|
||||
return NSVG_FILLRULE_NONZERO;
|
||||
}
|
||||
|
||||
static const char* nsvg__getNextDashItem(const char* s, char* it)
|
||||
{
|
||||
int n = 0;
|
||||
it[0] = '\0';
|
||||
// Skip white spaces and commas
|
||||
while (*s && (nsvg__isspace(*s) || *s == ',')) s++;
|
||||
// Advance until whitespace, comma or end.
|
||||
while (*s && (!nsvg__isspace(*s) && *s != ',')) {
|
||||
if (n < 63)
|
||||
it[n++] = *s;
|
||||
s++;
|
||||
}
|
||||
it[n++] = '\0';
|
||||
return s;
|
||||
}
|
||||
|
||||
static int nsvg__parseStrokeDashArray(NSVGparser* p, const char* str, float* strokeDashArray)
|
||||
{
|
||||
char item[64];
|
||||
int count = 0, i;
|
||||
float sum = 0.0f;
|
||||
|
||||
// Handle "none"
|
||||
if (str[0] == 'n')
|
||||
return 0;
|
||||
|
||||
// Parse dashes
|
||||
while (*str) {
|
||||
str = nsvg__getNextDashItem(str, item);
|
||||
if (!*item) break;
|
||||
if (count < NSVG_MAX_DASHES)
|
||||
strokeDashArray[count++] = fabsf(nsvg__parseCoordinate(p, item, 0.0f, nsvg__actualLength(p)));
|
||||
}
|
||||
|
||||
for (i = 0; i < count; i++)
|
||||
sum += strokeDashArray[i];
|
||||
if (sum <= 1e-6f)
|
||||
count = 0;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static void nsvg__parseStyle(NSVGparser* p, const char* str);
|
||||
|
||||
static int nsvg__parseAttr(NSVGparser* p, const char* name, const char* value)
|
||||
@ -1615,6 +1669,10 @@ static int nsvg__parseAttr(NSVGparser* p, const char* name, const char* value)
|
||||
}
|
||||
} else if (strcmp(name, "stroke-width") == 0) {
|
||||
attr->strokeWidth = nsvg__parseCoordinate(p, value, 0.0f, nsvg__actualLength(p));
|
||||
} else if (strcmp(name, "stroke-dasharray") == 0) {
|
||||
attr->strokeDashCount = nsvg__parseStrokeDashArray(p, value, attr->strokeDashArray);
|
||||
} else if (strcmp(name, "stroke-dashoffset") == 0) {
|
||||
attr->strokeDashOffset = nsvg__parseCoordinate(p, value, 0.0f, nsvg__actualLength(p));
|
||||
} else if (strcmp(name, "stroke-opacity") == 0) {
|
||||
attr->strokeOpacity = nsvg__parseOpacity(value);
|
||||
} else if (strcmp(name, "stroke-linecap") == 0) {
|
||||
@ -2632,7 +2690,7 @@ static void nsvg__scaleToViewbox(NSVGparser* p, const char* units)
|
||||
{
|
||||
NSVGshape* shape;
|
||||
NSVGpath* path;
|
||||
float tx, ty, sx, sy, us, bounds[4], t[6];
|
||||
float tx, ty, sx, sy, us, bounds[4], t[6], avgs;
|
||||
int i;
|
||||
float* pt;
|
||||
|
||||
@ -2683,6 +2741,7 @@ static void nsvg__scaleToViewbox(NSVGparser* p, const char* units)
|
||||
// Transform
|
||||
sx *= us;
|
||||
sy *= us;
|
||||
avgs = (sx+sy) / 2.0f;
|
||||
for (shape = p->image->shapes; shape != NULL; shape = shape->next) {
|
||||
shape->bounds[0] = (shape->bounds[0] + tx) * sx;
|
||||
shape->bounds[1] = (shape->bounds[1] + ty) * sy;
|
||||
@ -2711,6 +2770,10 @@ static void nsvg__scaleToViewbox(NSVGparser* p, const char* units)
|
||||
nsvg__xformInverse(shape->stroke.gradient->xform, t);
|
||||
}
|
||||
|
||||
shape->strokeWidth *= avgs;
|
||||
shape->strokeDashOffset *= avgs;
|
||||
for (i = 0; i < shape->strokeDashCount; i++)
|
||||
shape->strokeDashArray[i] *= avgs;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -128,6 +128,10 @@ struct NSVGrasterizer
|
||||
int npoints;
|
||||
int cpoints;
|
||||
|
||||
NSVGpoint* points2;
|
||||
int npoints2;
|
||||
int cpoints2;
|
||||
|
||||
NSVGactiveEdge* freelist;
|
||||
NSVGmemPage* pages;
|
||||
NSVGmemPage* curpage;
|
||||
@ -170,6 +174,7 @@ void nsvgDeleteRasterizer(NSVGrasterizer* r)
|
||||
|
||||
if (r->edges) free(r->edges);
|
||||
if (r->points) free(r->points);
|
||||
if (r->points2) free(r->points2);
|
||||
if (r->scanline) free(r->scanline);
|
||||
|
||||
free(r);
|
||||
@ -252,6 +257,29 @@ static void nsvg__addPathPoint(NSVGrasterizer* r, float x, float y, int flags)
|
||||
r->npoints++;
|
||||
}
|
||||
|
||||
static void nsvg__appendPathPoint(NSVGrasterizer* r, NSVGpoint pt)
|
||||
{
|
||||
if (r->npoints+1 > r->cpoints) {
|
||||
r->cpoints = r->cpoints > 0 ? r->cpoints * 2 : 64;
|
||||
r->points = (NSVGpoint*)realloc(r->points, sizeof(NSVGpoint) * r->cpoints);
|
||||
if (r->points == NULL) return;
|
||||
}
|
||||
r->points[r->npoints] = pt;
|
||||
r->npoints++;
|
||||
}
|
||||
|
||||
static void nsvg__duplicatePoints(NSVGrasterizer* r)
|
||||
{
|
||||
if (r->npoints > r->cpoints2) {
|
||||
r->cpoints2 = r->npoints;
|
||||
r->points2 = (NSVGpoint*)realloc(r->points2, sizeof(NSVGpoint) * r->cpoints2);
|
||||
if (r->points2 == NULL) return;
|
||||
}
|
||||
|
||||
memcpy(r->points2, r->points, sizeof(NSVGpoint) * r->npoints);
|
||||
r->npoints2 = r->npoints;
|
||||
}
|
||||
|
||||
static void nsvg__addEdge(NSVGrasterizer* r, float x0, float y0, float x1, float y1)
|
||||
{
|
||||
NSVGedge* e;
|
||||
@ -571,41 +599,84 @@ static int nsvg__curveDivs(float r, float arc, float tol)
|
||||
return divs;
|
||||
}
|
||||
|
||||
static void nsvg__flattenShapeStroke(NSVGrasterizer* r, NSVGshape* shape, float scale)
|
||||
static void nsvg__expandStroke(NSVGrasterizer* r, NSVGpoint* points, int npoints, int closed, int lineJoin, int lineCap, float lineWidth)
|
||||
{
|
||||
int i, j, closed;
|
||||
int s, e;
|
||||
NSVGpath* path;
|
||||
NSVGpoint* p0, *p1;
|
||||
float miterLimit = 4;
|
||||
int lineJoin = shape->strokeLineJoin;
|
||||
int lineCap = shape->strokeLineCap;
|
||||
float lineWidth = shape->strokeWidth * scale;
|
||||
int ncap = nsvg__curveDivs(lineWidth*0.5f, NSVG_PI, r->tessTol); // Calculate divisions per half circle.
|
||||
NSVGpoint left = {0,0,0,0,0,0,0,0}, right = {0,0,0,0,0,0,0,0}, firstLeft = {0,0,0,0,0,0,0,0}, firstRight = {0,0,0,0,0,0,0,0};
|
||||
NSVGpoint* p0, *p1;
|
||||
int j, s, e;
|
||||
|
||||
for (path = shape->paths; path != NULL; path = path->next) {
|
||||
r->npoints = 0;
|
||||
// Flatten path
|
||||
nsvg__addPathPoint(r, path->pts[0]*scale, path->pts[1]*scale, NSVG_PT_CORNER);
|
||||
for (i = 0; i < path->npts-1; i += 3) {
|
||||
float* p = &path->pts[i*2];
|
||||
nsvg__flattenCubicBez(r, p[0]*scale,p[1]*scale, p[2]*scale,p[3]*scale, p[4]*scale,p[5]*scale, p[6]*scale,p[7]*scale, 0, NSVG_PT_CORNER);
|
||||
// Build stroke edges
|
||||
if (closed) {
|
||||
// Looping
|
||||
p0 = &points[npoints-1];
|
||||
p1 = &points[0];
|
||||
s = 0;
|
||||
e = npoints;
|
||||
} else {
|
||||
// Add cap
|
||||
p0 = &points[0];
|
||||
p1 = &points[1];
|
||||
s = 1;
|
||||
e = npoints-1;
|
||||
}
|
||||
if (r->npoints < 2)
|
||||
continue;
|
||||
|
||||
closed = path->closed;
|
||||
if (closed) {
|
||||
nsvg__initClosed(&left, &right, p0, p1, lineWidth);
|
||||
firstLeft = left;
|
||||
firstRight = right;
|
||||
} else {
|
||||
// Add cap
|
||||
float dx = p1->x - p0->x;
|
||||
float dy = p1->y - p0->y;
|
||||
nsvg__normalize(&dx, &dy);
|
||||
if (lineCap == NSVG_CAP_BUTT)
|
||||
nsvg__buttCap(r, &left, &right, p0, dx, dy, lineWidth, 0);
|
||||
else if (lineCap == NSVG_CAP_SQUARE)
|
||||
nsvg__squareCap(r, &left, &right, p0, dx, dy, lineWidth, 0);
|
||||
else if (lineCap == NSVG_CAP_ROUND)
|
||||
nsvg__roundCap(r, &left, &right, p0, dx, dy, lineWidth, ncap, 0);
|
||||
}
|
||||
|
||||
for (j = s; j < e; ++j) {
|
||||
if (p1->flags & NSVG_PT_CORNER) {
|
||||
if (lineJoin == NSVG_JOIN_ROUND)
|
||||
nsvg__roundJoin(r, &left, &right, p0, p1, lineWidth, ncap);
|
||||
else if (lineJoin == NSVG_JOIN_BEVEL || (p1->flags & NSVG_PT_BEVEL))
|
||||
nsvg__bevelJoin(r, &left, &right, p0, p1, lineWidth);
|
||||
else
|
||||
nsvg__miterJoin(r, &left, &right, p0, p1, lineWidth);
|
||||
} else {
|
||||
nsvg__straightJoin(r, &left, &right, p1, lineWidth);
|
||||
}
|
||||
p0 = p1++;
|
||||
}
|
||||
|
||||
if (closed) {
|
||||
// Loop it
|
||||
nsvg__addEdge(r, firstLeft.x, firstLeft.y, left.x, left.y);
|
||||
nsvg__addEdge(r, right.x, right.y, firstRight.x, firstRight.y);
|
||||
} else {
|
||||
// Add cap
|
||||
float dx = p1->x - p0->x;
|
||||
float dy = p1->y - p0->y;
|
||||
nsvg__normalize(&dx, &dy);
|
||||
if (lineCap == NSVG_CAP_BUTT)
|
||||
nsvg__buttCap(r, &right, &left, p1, -dx, -dy, lineWidth, 1);
|
||||
else if (lineCap == NSVG_CAP_SQUARE)
|
||||
nsvg__squareCap(r, &right, &left, p1, -dx, -dy, lineWidth, 1);
|
||||
else if (lineCap == NSVG_CAP_ROUND)
|
||||
nsvg__roundCap(r, &right, &left, p1, -dx, -dy, lineWidth, ncap, 1);
|
||||
}
|
||||
}
|
||||
|
||||
static void nsvg__prepareStroke(NSVGrasterizer* r, float miterLimit, int lineJoin)
|
||||
{
|
||||
int i, j;
|
||||
NSVGpoint* p0, *p1;
|
||||
|
||||
// If the first and last points are the same, remove the last, mark as closed path.
|
||||
p0 = &r->points[r->npoints-1];
|
||||
p1 = &r->points[0];
|
||||
if (nsvg__ptEquals(p0->x,p0->y, p1->x,p1->y, r->distTol)) {
|
||||
r->npoints--;
|
||||
p0 = &r->points[r->npoints-1];
|
||||
closed = 1;
|
||||
}
|
||||
|
||||
for (i = 0; i < r->npoints; i++) {
|
||||
// Calculate segment direction and length
|
||||
p0->dx = p1->x - p0->x;
|
||||
@ -654,69 +725,113 @@ static void nsvg__flattenShapeStroke(NSVGrasterizer* r, NSVGshape* shape, float
|
||||
|
||||
p0 = p1++;
|
||||
}
|
||||
}
|
||||
|
||||
// Build stroke edges
|
||||
if (closed) {
|
||||
// Looping
|
||||
static void nsvg__flattenShapeStroke(NSVGrasterizer* r, NSVGshape* shape, float scale)
|
||||
{
|
||||
int i, j, closed;
|
||||
NSVGpath* path;
|
||||
NSVGpoint* p0, *p1;
|
||||
float miterLimit = 4;
|
||||
int lineJoin = shape->strokeLineJoin;
|
||||
int lineCap = shape->strokeLineCap;
|
||||
float lineWidth = shape->strokeWidth * scale;
|
||||
|
||||
for (path = shape->paths; path != NULL; path = path->next) {
|
||||
// Flatten path
|
||||
r->npoints = 0;
|
||||
nsvg__addPathPoint(r, path->pts[0]*scale, path->pts[1]*scale, NSVG_PT_CORNER);
|
||||
for (i = 0; i < path->npts-1; i += 3) {
|
||||
float* p = &path->pts[i*2];
|
||||
nsvg__flattenCubicBez(r, p[0]*scale,p[1]*scale, p[2]*scale,p[3]*scale, p[4]*scale,p[5]*scale, p[6]*scale,p[7]*scale, 0, NSVG_PT_CORNER);
|
||||
}
|
||||
if (r->npoints < 2)
|
||||
continue;
|
||||
|
||||
closed = path->closed;
|
||||
|
||||
// If the first and last points are the same, remove the last, mark as closed path.
|
||||
p0 = &r->points[r->npoints-1];
|
||||
p1 = &r->points[0];
|
||||
s = 0;
|
||||
e = r->npoints;
|
||||
} else {
|
||||
// Add cap
|
||||
p0 = &r->points[0];
|
||||
p1 = &r->points[1];
|
||||
s = 1;
|
||||
e = r->npoints-1;
|
||||
if (nsvg__ptEquals(p0->x,p0->y, p1->x,p1->y, r->distTol)) {
|
||||
r->npoints--;
|
||||
p0 = &r->points[r->npoints-1];
|
||||
closed = 1;
|
||||
}
|
||||
|
||||
if (closed) {
|
||||
nsvg__initClosed(&left, &right, p0, p1, lineWidth);
|
||||
firstLeft = left;
|
||||
firstRight = right;
|
||||
} else {
|
||||
// Add cap
|
||||
float dx = p1->x - p0->x;
|
||||
float dy = p1->y - p0->y;
|
||||
nsvg__normalize(&dx, &dy);
|
||||
if (lineCap == NSVG_CAP_BUTT)
|
||||
nsvg__buttCap(r, &left, &right, p0, dx, dy, lineWidth, 0);
|
||||
else if (lineCap == NSVG_CAP_SQUARE)
|
||||
nsvg__squareCap(r, &left, &right, p0, dx, dy, lineWidth, 0);
|
||||
else if (lineCap == NSVG_CAP_ROUND)
|
||||
nsvg__roundCap(r, &left, &right, p0, dx, dy, lineWidth, ncap, 0);
|
||||
}
|
||||
if (shape->strokeDashCount > 0) {
|
||||
int idash = 0, dashState = 1;
|
||||
float totalDist = 0, dashLen, allDashLen, dashOffset;
|
||||
NSVGpoint cur;
|
||||
|
||||
for (j = s; j < e; ++j) {
|
||||
// if (p1->flags & NSVG_PT_BEVEL) {
|
||||
if (p1->flags & NSVG_PT_CORNER) {
|
||||
if (lineJoin == NSVG_JOIN_ROUND)
|
||||
nsvg__roundJoin(r, &left, &right, p0, p1, lineWidth, ncap);
|
||||
else if (lineJoin == NSVG_JOIN_BEVEL || (p1->flags & NSVG_PT_BEVEL))
|
||||
nsvg__bevelJoin(r, &left, &right, p0, p1, lineWidth);
|
||||
else
|
||||
nsvg__miterJoin(r, &left, &right, p0, p1, lineWidth);
|
||||
} else {
|
||||
nsvg__straightJoin(r, &left, &right, p1, lineWidth);
|
||||
}
|
||||
p0 = p1++;
|
||||
}
|
||||
if (closed)
|
||||
nsvg__appendPathPoint(r, r->points[0]);
|
||||
|
||||
if (closed) {
|
||||
// Loop it
|
||||
nsvg__addEdge(r, firstLeft.x, firstLeft.y, left.x, left.y);
|
||||
nsvg__addEdge(r, right.x, right.y, firstRight.x, firstRight.y);
|
||||
// Duplicate points -> points2.
|
||||
nsvg__duplicatePoints(r);
|
||||
|
||||
r->npoints = 0;
|
||||
cur = r->points2[0];
|
||||
nsvg__appendPathPoint(r, cur);
|
||||
|
||||
// Figure out dash offset.
|
||||
allDashLen = 0;
|
||||
for (j = 0; j < shape->strokeDashCount; j++)
|
||||
allDashLen += shape->strokeDashArray[j];
|
||||
if (shape->strokeDashCount & 1)
|
||||
allDashLen *= 2.0f;
|
||||
// Find location inside pattern
|
||||
dashOffset = fmodf(shape->strokeDashOffset, allDashLen);
|
||||
if (dashOffset < 0.0f)
|
||||
dashOffset += allDashLen;
|
||||
|
||||
while (dashOffset > shape->strokeDashArray[idash]) {
|
||||
dashOffset -= shape->strokeDashArray[idash];
|
||||
idash = (idash + 1) % shape->strokeDashCount;
|
||||
}
|
||||
dashLen = (shape->strokeDashArray[idash] - dashOffset) * scale;
|
||||
|
||||
for (j = 1; j < r->npoints2; ) {
|
||||
float dx = r->points2[j].x - cur.x;
|
||||
float dy = r->points2[j].y - cur.y;
|
||||
float dist = sqrtf(dx*dx + dy*dy);
|
||||
|
||||
if ((totalDist + dist) > dashLen) {
|
||||
// Calculate intermediate point
|
||||
float d = (dashLen - totalDist) / dist;
|
||||
float x = cur.x + dx * d;
|
||||
float y = cur.y + dy * d;
|
||||
nsvg__addPathPoint(r, x, y, NSVG_PT_CORNER);
|
||||
|
||||
// Stroke
|
||||
if (r->npoints > 1 && dashState) {
|
||||
nsvg__prepareStroke(r, miterLimit, lineJoin);
|
||||
nsvg__expandStroke(r, r->points, r->npoints, 0, lineJoin, lineCap, lineWidth);
|
||||
}
|
||||
// Advance dash pattern
|
||||
dashState = !dashState;
|
||||
idash = (idash+1) % shape->strokeDashCount;
|
||||
dashLen = shape->strokeDashArray[idash] * scale;
|
||||
// Restart
|
||||
cur.x = x;
|
||||
cur.y = y;
|
||||
cur.flags = NSVG_PT_CORNER;
|
||||
totalDist = 0.0f;
|
||||
r->npoints = 0;
|
||||
nsvg__appendPathPoint(r, cur);
|
||||
} else {
|
||||
// Add cap
|
||||
float dx = p1->x - p0->x;
|
||||
float dy = p1->y - p0->y;
|
||||
nsvg__normalize(&dx, &dy);
|
||||
if (lineCap == NSVG_CAP_BUTT)
|
||||
nsvg__buttCap(r, &right, &left, p1, -dx, -dy, lineWidth, 1);
|
||||
else if (lineCap == NSVG_CAP_SQUARE)
|
||||
nsvg__squareCap(r, &right, &left, p1, -dx, -dy, lineWidth, 1);
|
||||
else if (lineCap == NSVG_CAP_ROUND)
|
||||
nsvg__roundCap(r, &right, &left, p1, -dx, -dy, lineWidth, ncap, 1);
|
||||
totalDist += dist;
|
||||
cur = r->points2[j];
|
||||
nsvg__appendPathPoint(r, cur);
|
||||
j++;
|
||||
}
|
||||
}
|
||||
// Stroke any leftover path
|
||||
if (r->npoints > 1 && dashState)
|
||||
nsvg__expandStroke(r, r->points, r->npoints, 0, lineJoin, lineCap, lineWidth);
|
||||
} else {
|
||||
nsvg__prepareStroke(r, miterLimit, lineJoin);
|
||||
nsvg__expandStroke(r, r->points, r->npoints, closed, lineJoin, lineCap, lineWidth);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user