Proposals/Variable width stroke

From SVG

Syntax proposal

Proposal:

 stroke-profile-widths: [ [<percentage> | <length>] [/ [<percentage> | <length>] ]? ]#
 stroke-profile-positions: [<percentage | <length>]# | segment-start
 stroke-profile-repeat: no-repeat | repeat (Initial: no-repeat; future value: reflect?)
 stroke-profile: [<value> <position>?]# <repeat>?

Widths list:

  • <percentage> values represent percentages of the computed value of the stroke-width property.
  • Only positive values are permitted. Negative values are clamped to zero.
  • If two numbers as specified they represent the width on the left-side of the stroke (/) and the right-side of the stroke respectively.
  • If only one number is specified, the stroke width is symmetrical.

Positions list:

  • <percentage> values represent percentages of the path length.
    • Issue 1: Should these values be relative? This would line-up with proposed dash segment syntax and make a long sequence of segment-aligned widths easier to write (Need to discuss with Cameron how this should work)
  • Add a 'seg' length unit that is a floating-point number where the integer part is a zero-based index into the segments in the context path (e.g. 0 represents the segment created by the first path command that is not a moveto command) and the fractional part is an offset into the segment based on the length of the segment. e.g. 3.5 represents the mid-point of the fourth drawn segment.
    • moveto segments and bearing segments are not counted
    • The fractional part is based on the path length, not the t value
  • If there are more values than positions? → as with CSS gradients, set position of last value to 100% and space other values out (using percentages of the path length)
    • See issue under use case (d). We'd like a mode that doesn't require specifying all the positions when they are segment-aligned
  • If there are more positions than values? → ignore extra positions
  • If there are gaps in the list of positions (when using the short-hand)? → as with CSS gradients, evenly space out values (based on percentages of the path length)
  • If the list of positions is not in ascending order? → as with CSS gradients, clamp each value to max(<prev-position>, <position>)
  • Range of percentage positions? → as with CSS gradients, values < 0% and > 100% are permitted
    • Issue 2: Is this a good idea? It means you have to extrapolate the path to get the correct result. (It's needed because you might specify widths beyond the end of the path using absolute values anyway.)

Repeating:

  • no-repeat means that if the final position is less than 100%, the stroke-width remains fixed at the final stroke width value for the remainder of the path.
  • repeat means that if the final position is less than 100% take the computed value of the positions and repeat that by adding them onto the final position value.

Syntax examples

a) A hair stroke pattern

 .hair {
   stroke-width: 10px;
   stroke-profile-widths: 10%, 110%, 30%;
   stroke-profile-positions: 0, 70%;
   stroke-linecap: round;
 }

Or with the shorthand:

 .hair {
   stroke-width: 10px;
   stroke-profile: 10% 0, 110% 70%, 30%;
   stroke-linecap: round;
 }

b) A bulging pattern like a snake that has eaten a lot of eggs.

 .snake {
   stroke-width: 30px;
   stroke-profile: 100% 0, 50% 30px, 100% 60px repeat;
 }

c) An n-pointed star where the outside points and inside points differ in stroke width

 star.sharp {
   stroke-profile: 100% 0seg, 150% 1seg, 100% 2seg repeat;
 }

d) A path created using touch input where the stroke widths correspond to touch pressure

 <path d="M123 345C...." stroke-profile="1 0seg, 1.1 1seg, 1.15 2seg, 0.8 3seg, ..." />

Issue 3: It would be nice to be able to simplify this by having a special mode where every value aligns with a segment. This would reduce file size and also make editing path data simpler (particularly if you are removing points). (Suggestion to add a keyword for this.)

Prototype implementation

https://rawgithub.com/birtles/curvy/master/index.html

Questions

From: http://lists.w3.org/Archives/Public/www-svg/2013May/0041.html

  • Will there be a proper fallback: a specific feature name usable in svg:switch?
  • How will variable stroke-widths interact with vector effects?

(Could it actually be implemented as a vector effect so that if problems are found a new vector effect could be created without invalidating the original one?)

  • How will stroke position work with this? (There is high demand for adjustable stroke positioning. If we have asymmetric widths you can be sure it will be used to simulate this. Maybe we should just bite the bullet spec stroke position now.)
  • How are dashes handled (e.g. dash caps)?
  • Can other properties depend on path position, e.g. color?
  • How will line joins be handled with repeated patterns where the beginning and ending widths are different and the path segments are at an angle?

Comments:

  • Inkscape's Power Stroke implementation:

I was slightly wrong about how Inkscape determines the position along a path. The fractional part is actually the 'time value' of the Bezier curve as was easier to use that the actual distance. Thus:

 unit part -> node index
 fractional part -> 'time value'
  • Mixing linear and smooth parts:

Could be done by adding a smoothness type for each point. Inkscape's Spiro LPE uses the following notation:

 c 0, 10px --> curved
 c 1, 20px
 ] 2, 10px --> left of node curved, linear on right
 s 2.5, 15px --> linear
 [ 3, 10px --> right of node curved, linear on left
 c 4, 20px
 c 5, 10px

Synfig uses a smoothness value the interpolates between a smoothed curve and a straight line.

  • Positioning using percentage is more computationally intensive if only part of path shown.

Other implementations of variable stroke width:

  • Synfig:

http://wiki.synfig.org/wiki/Advanced_Outline_Layer

Positioning along length either:

 Homogeneous: based on percentage of length.
 Non Homogeneous: based on percentage of points (a three Bezier path has four points so 0.5 means middle of second Bezier).
 Width is percentage of nominal stroke width, negative values allowed.
 Smoothing done by 5th degree spline smooth function in linear combination with linear interpolation.
 A smoothness parameter allows change between smooth and linear. See:
 https://github.com/synfig/synfig/blob/master/synfig-core/src/synfig/valuenode_wplist.cpp#L107

End caps are discontinuous when smoothness is zero and continuous when smoothness is maximum.

With dashed lines, allows custom caps and a variable width within dash.

Not yet in Wiki: Complex method to keep width control points at same point along path when extending a path (needed when several hand-drawn strokes are merged into one path). (I think Inkscape's method deals with this better.)

  • Graffiti markup:

http://www.graffitimarkuplanguage.com/about/

http://www.graffitimarkuplanguage.com/g-m-l-spec/

Includes parameters for speed, drip, rotation, etc.