RE: Thoughts about variable-width strokes in SVG2

I have read only parts of the discussion that follows this but it seems like the use case being proposed as well as the way of conceptualizing the task is a bit different than the way I think of it.

It seems to me that 
a) the use case for variable width stroke are broader than any single user-interface for producing them. There are lots of user interfaces possible (including that at [1] discussed later), so thinking about what the properties of the curves are and the parsimony of the code is more important. To me the elegance of SVG lies not in the various programs people may write to create it as output but in the generally small footprint of the code itself and the cognitive impact that has on people who wish to script a scene. On the one extreme we have WebGL that is virtually package-dependent -- who makes meshes by hand coding? Designing features into SVG that relay on packages for their creation, seems fundamentally contrary to the philosophy of the language to me.

b) Two conceptual approaches seem simpler than having a huge vector of thickness coefficients applied to various points of a curve (as it seems like Brian is suggesting) -- rather like shaders attached to meshes or some darn thing requiring heavy baggage external to SVG:

b1 -- Let a variable-width stroke (VWS) be defined by an SVG path plus another SVG curve that describes the width and height of the curve at that point as seen in [1]. Rather than having a slew of thickness values attached to a curve, let us attach a functional relationship (in 2D ideally) to the curve. Of course <replicate> would make this altogether too easy conceptually -- something that some members of the SVG WG have been working hard against for years!

b2 -- Let a VWS curve be described by two paths -- one describing its "top" edge, the other, its "bottom" edge. The curve is merely filled with a gradient (ideally a rich gradient such as made by something like a higher dimensional Bezier or even <replicate>). The advantage of this approach would be that SVG would then have the proper conceptual framework to begin addresses text warping, which seems to require both a top and baseline along which the geometry of glyphs is laid and shaped.


[1]  http://cs.sru.edu/~ddailey/svg/pathRep2JS.svg -- The yellow dots control the position of the curve, the green ones control its x and y thickness at that point.

Cheers
David

-----Original Message-----
From: Brian Birtles [mailto:bbirtles@mozilla.com] 
Sent: Monday, May 06, 2013 11:12 PM
To: www-svg
Subject: Thoughts about variable-width strokes in SVG2

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.

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 
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 13:43:41 UTC