Thoughts about variable-width strokes in SVG2


We have previously resolved to add variable-width stroke to SVG2 and I 
thought I'd contribute some suggestions about how to spec it.

There are a number of ways of representing variable-width stroke so I've 
tried to narrow down the options down based on a particular use case I have.

Some of the questions to address:

1) How should we position the widths?

   Some possibilities:

   A) Associate stroke widths with percentage offsets along the path. 
(Where offsets are omitted, widths are evenly distributed between those 
widths which have an assigned offset based on path length.)

   B) Associate stroke widths with absolute offsets along the path 
(adjusted by pathLength). (Where offsets are omitted, the behavior is as 
with (A).) Setting pathLength would basically make this the same as (A).

   C) Associate stroke widths with the points in the path (i.e. you 
don't specify any offsets but just treat them as parallel arrays)

   When the number of widths doesn't match the number of points in the 
path you'd spread the widths out along the path based on *index*. 
Basically, you initially assume the points are evenly spaced and 
distribute the widths along the path. For example, if you had three 
widths, the first width would specify the width at the start point, the 
second width would specify the width at the median point in the path (or 
mid-way between the two middle points if you have an even number of 
points) and the last width would specify the width at the end of the path.

2) Should the widths be absolute lengths or magnification factors?

3) Should the widths be a single symmetrical value applied to both sides 
of the path or allow it to be asymmetrical?

4) Should the widths be allowed to be negative? (This mostly only makes 
sense if you allow asymmetrical values)

The use case I have: a pointer-event based drawing app.

Relevant features:

* Paths are built up in response to pointer events.
* Initially points are just tacked only a <polyline> but then when the 
interaction completes (touchend/mouseup etc.) it is converted to a 
<path> by applying some smoothing.
* The smoothing may remove points.
* The smoothing may change the length of the path due to the values 
chosen for the cubic Bezier control points.
* The radius of the pointer event (or pressure if we can get some 
reliable readings) is used to control the stroke width at that point.
* The user can erase parts of the path using pointer events. We detect 
intersections and subdivide the path accordingly. Typically we just 
remove points and create a new subpath (we may add points but generally 
the points are so close together its not necessary).

Regarding question 1 (how to position the widths), option (A) is 
problematic. Every time you add a point to the <polyline> you would have 
to adjust all the other stroke width offsets since they are based on a 
percentage of the path length and the path length has changed. That's 
clumsy and slow (and speed really matters for this app on mobile).

Option (B) might be do-able. You'd have to calculate the pathLength each 
time you got a point and unfortunately the pathLength member is 
specified on SVGPathElement, but not SVGPolylineElement. You'd probably 
do it with script by building up a cumulative path length each time you 
added a point.

Option (C) is the most natural fit for this application: you get a 
pointer event with a co-ordinate and a radius/pressure reading at the 
same time. You simply append the coordinate to the list of points and 
the appropriate stroke width to the list of widths. Easy and fast 
(except that the UA has to re-parse the entire stroke-widths property, 
but that's a problem with all approaches).

How about smoothing and erasing?

If, as a part of smoothing/erasing you remove the point at index 37, 
what you probably want is to remove the stroke-width at that point. 
Obviously option (C) is easiest for this since it's trivial to remove 
the stroke-width at the same offset.

If, as a part of smoothing, you add bezier control points that make the 
path length longer, again, option (C) is probably going to give you the 
most intuitive results since the stroke widths don't shift in position.

If, as a part of erasing you subdivide a long segment, add points and 
create a new subpath, it gets more complicated. For (A) and (B) do 
percentages apply to the path or to each sub-path? Presumably it is to 
the path as a whole. To stop other widths from moving around now that 
the pathLength has changed you'd probably have to adjust them all 
accordingly. For (C) you'd probably just interpolate adjacent stroke 
width values and apply them to the newly created points.

Regarding question 2, this application could use either absolute stroke 
widths or magnification factors.

For pressure, magnification factors are probably more useful. For 
example, you set the stroke-width to '15px' based on the drawing tool 
chosen, then, based on the pressure you assign stroke-widths: 0.8, 1.1 etc.

If you're adjusting the stroke width based on the radius of the 
pointer-event, an absolute stroke width may make more sense.

Overall, my hunch is magnification factors would be best since it allows 
you to fatten/thin the whole path easily, but that's just a guess. It's 
probably possible to support both if, for example, we allowed each 
positioned stroke width to be specified as <percentage>|<length> like we 
already do for stroke-width.

Regarding question 3, this application does not need asymmetrical stroke 

Regarding question 4, this application does not need negative stroke widths.

Of course this is not the only application but it's a real-world one 
(and I suspect a common one) that would benefit from variable stroke 
width right now.

I'm less interested in guessing about other applications although I'd be 
prepared to concede that using stroke-widths to taper off the end of a 
long stroke *might* be common. For that case we could either say:

- Deal with it later using some as-yet unspecified addition to 
variable-width stroking
- Deal with it now using variable-width stroking by allowing an optional 
(index-based?) offset
- Deal with it now or later by another means such as an extension of 

In summary:
* For drawing-style apps, variable stroke-width is easiest to use when 
the individual widths are tied to points in the path.
* Some work is probably required to work out the best algorithm for 
lining up the widths when the number of widths differs from the number 
of points in the path.
* The ability to fix a given width to a particular offset *might* be 
useful for tapering etc.
* Both absolute stroke widths and magnification factors would be acceptable.
* Asymmetrical stroke widths may not be necessary.
* Negative stroke widths may not be necessary.

Best regards,


Received on Tuesday, 7 May 2013 03:12:36 UTC