- From: David Dailey <ddailey@zoominternet.net>
- Date: Tue, 7 May 2013 09:43:06 -0400
- To: "'Brian Birtles'" <bbirtles@mozilla.com>, "'www-svg'" <www-svg@w3.org>
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