Re: Thoughts about variable-width strokes in SVG2

On May 6, 2013, at 9:00 PM, Rik Cabanier <cabanier@gmail.com> wrote:

> 
> 
> On Mon, May 6, 2013 at 8:12 PM, Brian Birtles <bbirtles@mozilla.com> 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.)
> 
> I think this makes the most sense.
>  
> 
>   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?
> 
> They should be magnification so you can apply different strokewidths
>  
> 
> 3) Should the widths be a single symmetrical value applied to both sides of the path or allow it to be asymmetrical?
> 
> We shouldn't make it assymmetrical (for now). Otherwise the author will have to figure out if there's an inside/outside of a path

Agree. The syntax should still be specified in a way that it is possible to add it in the future.

>  
> 
> 4) Should the widths be allowed to be negative? (This mostly only makes sense if you allow asymmetrical values)
> 
> No.
>  
> 
> 
> 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).
> 
> How common would that be?
> Even so, you have to stroke again if you add a point so the calculation needs to be redone anyway.
>  
> 
> 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 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. 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 Tuesday, 7 May 2013 04:19:02 UTC