Re: Thoughts about variable-width strokes in SVG2

On Tue, 2013-05-07 at 12:12 +0900, Brian Birtles wrote:
> Hi,
> 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.

Inkscape's "Power Stroke" Live Path Effect (PS-LPE) uses an array of
offset points with two numbers where the integer part of the first
number dictates between which set of points the offset is placed, the
fractional part gives the proportion of how far between the two points
the offset is placed, and the second number gives the perpendicular
offset at that place. This seems to work quite will when moving
points/nodes around as is typical in a drawing program.

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

The PS-LPE uses absolute lengths. This is probably not so important.

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

It is symmetric.

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

Negative values are allowed (useful for creating twisted paths?).

> 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.

The PS-LPE simply shifts the integer part when points (nodes) are added
to or removed from the original path

> 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 
> widths.
> 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.

Yes, I think so.

> 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 
> stroke-linecap.
> 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,
> Brian

Received on Thursday, 9 May 2013 20:26:01 UTC