Implemented dashed stroke rendering

This commit is contained in:
Mikko Mononen 2015-11-07 23:19:30 +02:00
parent 7efce85328
commit dc75508682
2 changed files with 287 additions and 109 deletions

View File

@ -140,6 +140,9 @@ typedef struct NSVGshape
NSVGpaint stroke; // Stroke paint NSVGpaint stroke; // Stroke paint
float opacity; // Opacity of the shape. float opacity; // Opacity of the shape.
float strokeWidth; // Stroke width (scaled). 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 strokeLineJoin; // Stroke join type.
char strokeLineCap; // Stroke cap type. char strokeLineCap; // Stroke cap type.
char fillRule; // Fill rule, see NSVGfillRule. char fillRule; // Fill rule, see NSVGfillRule.
@ -348,6 +351,8 @@ enum NSVGgradientUnits {
NSVG_OBJECT_SPACE = 1, NSVG_OBJECT_SPACE = 1,
}; };
#define NSVG_MAX_DASHES 8
enum NSVGunits { enum NSVGunits {
NSVG_UNITS_USER, NSVG_UNITS_USER,
NSVG_UNITS_PX, NSVG_UNITS_PX,
@ -403,6 +408,9 @@ typedef struct NSVGattrib
char fillGradient[64]; char fillGradient[64];
char strokeGradient[64]; char strokeGradient[64];
float strokeWidth; float strokeWidth;
float strokeDashOffset;
float strokeDashArray[NSVG_MAX_DASHES];
int strokeDashCount;
char strokeLineJoin; char strokeLineJoin;
char strokeLineCap; char strokeLineCap;
char fillRule; char fillRule;
@ -614,7 +622,6 @@ static NSVGparser* nsvg__createParser()
p->attr[0].strokeLineCap = NSVG_CAP_BUTT; p->attr[0].strokeLineCap = NSVG_CAP_BUTT;
p->attr[0].fillRule = NSVG_FILLRULE_NONZERO; p->attr[0].fillRule = NSVG_FILLRULE_NONZERO;
p->attr[0].hasFill = 1; p->attr[0].hasFill = 1;
p->attr[0].hasStroke = 0;
p->attr[0].visible = 1; p->attr[0].visible = 1;
return p; return p;
@ -913,6 +920,7 @@ static void nsvg__addShape(NSVGparser* p)
float scale = 1.0f; float scale = 1.0f;
NSVGshape *shape, *cur, *prev; NSVGshape *shape, *cur, *prev;
NSVGpath* path; NSVGpath* path;
int i;
if (p->plist == NULL) if (p->plist == NULL)
return; return;
@ -924,6 +932,10 @@ static void nsvg__addShape(NSVGparser* p)
memcpy(shape->id, attr->id, sizeof shape->id); memcpy(shape->id, attr->id, sizeof shape->id);
scale = nsvg__getAverageScale(attr->xform); scale = nsvg__getAverageScale(attr->xform);
shape->strokeWidth = attr->strokeWidth * scale; 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->strokeLineJoin = attr->strokeLineJoin;
shape->strokeLineCap = attr->strokeLineCap; shape->strokeLineCap = attr->strokeLineCap;
shape->fillRule = attr->fillRule; shape->fillRule = attr->fillRule;
@ -1574,6 +1586,48 @@ static char nsvg__parseFillRule(const char* str)
return NSVG_FILLRULE_NONZERO; 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 void nsvg__parseStyle(NSVGparser* p, const char* str);
static int nsvg__parseAttr(NSVGparser* p, const char* name, const char* value) 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) { } else if (strcmp(name, "stroke-width") == 0) {
attr->strokeWidth = nsvg__parseCoordinate(p, value, 0.0f, nsvg__actualLength(p)); 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) { } else if (strcmp(name, "stroke-opacity") == 0) {
attr->strokeOpacity = nsvg__parseOpacity(value); attr->strokeOpacity = nsvg__parseOpacity(value);
} else if (strcmp(name, "stroke-linecap") == 0) { } else if (strcmp(name, "stroke-linecap") == 0) {
@ -2632,7 +2690,7 @@ static void nsvg__scaleToViewbox(NSVGparser* p, const char* units)
{ {
NSVGshape* shape; NSVGshape* shape;
NSVGpath* path; 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; int i;
float* pt; float* pt;
@ -2683,6 +2741,7 @@ static void nsvg__scaleToViewbox(NSVGparser* p, const char* units)
// Transform // Transform
sx *= us; sx *= us;
sy *= us; sy *= us;
avgs = (sx+sy) / 2.0f;
for (shape = p->image->shapes; shape != NULL; shape = shape->next) { for (shape = p->image->shapes; shape != NULL; shape = shape->next) {
shape->bounds[0] = (shape->bounds[0] + tx) * sx; shape->bounds[0] = (shape->bounds[0] + tx) * sx;
shape->bounds[1] = (shape->bounds[1] + ty) * sy; 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); nsvg__xformInverse(shape->stroke.gradient->xform, t);
} }
shape->strokeWidth *= avgs;
shape->strokeDashOffset *= avgs;
for (i = 0; i < shape->strokeDashCount; i++)
shape->strokeDashArray[i] *= avgs;
} }
} }

View File

@ -128,6 +128,10 @@ struct NSVGrasterizer
int npoints; int npoints;
int cpoints; int cpoints;
NSVGpoint* points2;
int npoints2;
int cpoints2;
NSVGactiveEdge* freelist; NSVGactiveEdge* freelist;
NSVGmemPage* pages; NSVGmemPage* pages;
NSVGmemPage* curpage; NSVGmemPage* curpage;
@ -170,6 +174,7 @@ void nsvgDeleteRasterizer(NSVGrasterizer* r)
if (r->edges) free(r->edges); if (r->edges) free(r->edges);
if (r->points) free(r->points); if (r->points) free(r->points);
if (r->points2) free(r->points2);
if (r->scanline) free(r->scanline); if (r->scanline) free(r->scanline);
free(r); free(r);
@ -252,6 +257,29 @@ static void nsvg__addPathPoint(NSVGrasterizer* r, float x, float y, int flags)
r->npoints++; 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) static void nsvg__addEdge(NSVGrasterizer* r, float x0, float y0, float x1, float y1)
{ {
NSVGedge* e; NSVGedge* e;
@ -571,22 +599,147 @@ static int nsvg__curveDivs(float r, float arc, float tol)
return divs; return divs;
} }
static void nsvg__expandStroke(NSVGrasterizer* r, NSVGpoint* points, int npoints, int closed, int lineJoin, int lineCap, float lineWidth)
{
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;
// 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 (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;
p0 = &r->points[r->npoints-1];
p1 = &r->points[0];
for (i = 0; i < r->npoints; i++) {
// Calculate segment direction and length
p0->dx = p1->x - p0->x;
p0->dy = p1->y - p0->y;
p0->len = nsvg__normalize(&p0->dx, &p0->dy);
// Advance
p0 = p1++;
}
// calculate joins
p0 = &r->points[r->npoints-1];
p1 = &r->points[0];
for (j = 0; j < r->npoints; j++) {
float dlx0, dly0, dlx1, dly1, dmr2, cross;
dlx0 = p0->dy;
dly0 = -p0->dx;
dlx1 = p1->dy;
dly1 = -p1->dx;
// Calculate extrusions
p1->dmx = (dlx0 + dlx1) * 0.5f;
p1->dmy = (dly0 + dly1) * 0.5f;
dmr2 = p1->dmx*p1->dmx + p1->dmy*p1->dmy;
if (dmr2 > 0.000001f) {
float s2 = 1.0f / dmr2;
if (s2 > 600.0f) {
s2 = 600.0f;
}
p1->dmx *= s2;
p1->dmy *= s2;
}
// Clear flags, but keep the corner.
p1->flags = (p1->flags & NSVG_PT_CORNER) ? NSVG_PT_CORNER : 0;
// Keep track of left turns.
cross = p1->dx * p0->dy - p0->dx * p1->dy;
if (cross > 0.0f)
p1->flags |= NSVG_PT_LEFT;
// Check to see if the corner needs to be beveled.
if (p1->flags & NSVG_PT_CORNER) {
if ((dmr2 * miterLimit*miterLimit) < 1.0f || lineJoin == NSVG_JOIN_BEVEL || lineJoin == NSVG_JOIN_ROUND) {
p1->flags |= NSVG_PT_BEVEL;
}
}
p0 = p1++;
}
}
static void nsvg__flattenShapeStroke(NSVGrasterizer* r, NSVGshape* shape, float scale) static void nsvg__flattenShapeStroke(NSVGrasterizer* r, NSVGshape* shape, float scale)
{ {
int i, j, closed; int i, j, closed;
int s, e;
NSVGpath* path; NSVGpath* path;
NSVGpoint* p0, *p1; NSVGpoint* p0, *p1;
float miterLimit = 4; float miterLimit = 4;
int lineJoin = shape->strokeLineJoin; int lineJoin = shape->strokeLineJoin;
int lineCap = shape->strokeLineCap; int lineCap = shape->strokeLineCap;
float lineWidth = shape->strokeWidth * scale; 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};
for (path = shape->paths; path != NULL; path = path->next) { for (path = shape->paths; path != NULL; path = path->next) {
r->npoints = 0;
// Flatten path // Flatten path
r->npoints = 0;
nsvg__addPathPoint(r, path->pts[0]*scale, path->pts[1]*scale, NSVG_PT_CORNER); nsvg__addPathPoint(r, path->pts[0]*scale, path->pts[1]*scale, NSVG_PT_CORNER);
for (i = 0; i < path->npts-1; i += 3) { for (i = 0; i < path->npts-1; i += 3) {
float* p = &path->pts[i*2]; float* p = &path->pts[i*2];
@ -606,117 +759,79 @@ static void nsvg__flattenShapeStroke(NSVGrasterizer* r, NSVGshape* shape, float
closed = 1; closed = 1;
} }
for (i = 0; i < r->npoints; i++) { if (shape->strokeDashCount > 0) {
// Calculate segment direction and length int idash = 0, dashState = 1;
p0->dx = p1->x - p0->x; float totalDist = 0, dashLen, allDashLen, dashOffset;
p0->dy = p1->y - p0->y; NSVGpoint cur;
p0->len = nsvg__normalize(&p0->dx, &p0->dy);
// Advance
p0 = p1++;
}
// calculate joins if (closed)
p0 = &r->points[r->npoints-1]; nsvg__appendPathPoint(r, r->points[0]);
p1 = &r->points[0];
for (j = 0; j < r->npoints; j++) { // Duplicate points -> points2.
float dlx0, dly0, dlx1, dly1, dmr2, cross; nsvg__duplicatePoints(r);
dlx0 = p0->dy;
dly0 = -p0->dx; r->npoints = 0;
dlx1 = p1->dy; cur = r->points2[0];
dly1 = -p1->dx; nsvg__appendPathPoint(r, cur);
// Calculate extrusions
p1->dmx = (dlx0 + dlx1) * 0.5f; // Figure out dash offset.
p1->dmy = (dly0 + dly1) * 0.5f; allDashLen = 0;
dmr2 = p1->dmx*p1->dmx + p1->dmy*p1->dmy; for (j = 0; j < shape->strokeDashCount; j++)
if (dmr2 > 0.000001f) { allDashLen += shape->strokeDashArray[j];
float s2 = 1.0f / dmr2; if (shape->strokeDashCount & 1)
if (s2 > 600.0f) { allDashLen *= 2.0f;
s2 = 600.0f; // Find location inside pattern
} dashOffset = fmodf(shape->strokeDashOffset, allDashLen);
p1->dmx *= s2; if (dashOffset < 0.0f)
p1->dmy *= s2; dashOffset += allDashLen;
while (dashOffset > shape->strokeDashArray[idash]) {
dashOffset -= shape->strokeDashArray[idash];
idash = (idash + 1) % shape->strokeDashCount;
} }
dashLen = (shape->strokeDashArray[idash] - dashOffset) * scale;
// Clear flags, but keep the corner. for (j = 1; j < r->npoints2; ) {
p1->flags = (p1->flags & NSVG_PT_CORNER) ? NSVG_PT_CORNER : 0; float dx = r->points2[j].x - cur.x;
float dy = r->points2[j].y - cur.y;
float dist = sqrtf(dx*dx + dy*dy);
// Keep track of left turns. if ((totalDist + dist) > dashLen) {
cross = p1->dx * p0->dy - p0->dx * p1->dy; // Calculate intermediate point
if (cross > 0.0f) float d = (dashLen - totalDist) / dist;
p1->flags |= NSVG_PT_LEFT; float x = cur.x + dx * d;
float y = cur.y + dy * d;
nsvg__addPathPoint(r, x, y, NSVG_PT_CORNER);
// Check to see if the corner needs to be beveled. // Stroke
if (p1->flags & NSVG_PT_CORNER) { if (r->npoints > 1 && dashState) {
if ((dmr2 * miterLimit*miterLimit) < 1.0f || lineJoin == NSVG_JOIN_BEVEL || lineJoin == NSVG_JOIN_ROUND) { nsvg__prepareStroke(r, miterLimit, lineJoin);
p1->flags |= NSVG_PT_BEVEL; 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 {
totalDist += dist;
cur = r->points2[j];
nsvg__appendPathPoint(r, cur);
j++;
} }
} }
// Stroke any leftover path
p0 = p1++; if (r->npoints > 1 && dashState)
} nsvg__expandStroke(r, r->points, r->npoints, 0, lineJoin, lineCap, lineWidth);
// Build stroke edges
if (closed) {
// Looping
p0 = &r->points[r->npoints-1];
p1 = &r->points[0];
s = 0;
e = r->npoints;
} else { } else {
// Add cap nsvg__prepareStroke(r, miterLimit, lineJoin);
p0 = &r->points[0]; nsvg__expandStroke(r, r->points, r->npoints, closed, lineJoin, lineCap, lineWidth);
p1 = &r->points[1];
s = 1;
e = r->npoints-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);
}
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) {
// 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);
} }
} }
} }