3

Are there any vector graphics standards that support variable-thickness paths / strokes, e.g. from a stylus input:

enter image description here

Some amount of smoothing may be acceptable. I'd assume that the best way to store it would be as a regular path (e.g. this) and then point-wise sparse thickness information at various points in the path, with gradients between them.

I have looked at SVG but there doesn't seem to be an element that can support it. Are there any vector graphics standards that can?

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Peeyush Kushwaha
  • 3,453
  • 8
  • 35
  • 69

2 Answers2

2

A single path as currently implemented does not allow variable thickness. There is a W3.org proposal for SVG standard, but no implementation so far in pure SVG.

There are several implementation of a "path with variable thickness", but that relies on svg objects (eg., multiple paths) and a c++ or javascript functions.

  • PowerStroke is an implementation of such idea of a variable thickness stroke in Inkscape. A good entry to the source in c++ is here.

There are other implementations in SVG and javascript, relying on multiple paths:

  • Tubefy, a set of few js functions, the principle is based on a linear interpolation. There are several implementation of Tubefy, the simplest is:

     $ = function (id) { return typeof id=='string'?document.getElementById(id):id };
     var root = document.rootElement;
    
     function lerp(p, a, b) { return Number(a)+(b-a)*p; }
    
     function lerpA(p, a, b) { var c=[];
         for(var i=0; i<a.length; i++) c[i]=lerp(p, a[i], b[i]);
         return c;
     }
    
     function toCss(a){ 
         for(var i=0; i<a.length; i++) a[i]=Math.round(a[i]);
         return "rgb(" + a.join() + ")";
     }
    
  • Variable Stroke-Width, based on multiple path, which could be the best answer to your needs.

In one of the examples, the js function uses Tubefy and is directly implemented in the svg file:

<script>//<![CDATA[
    var op=1, op1=1;

    function vsw0(p0, n, g){    p0=$(p0);
        var SW=p0.getAttribute('stroke-widths').replace(/ /g,'').split(',');
        var T=p0.getTotalLength();
        var n_1=n-1,  dt=T/n, dash=(dt+1)+','+T;
        p0.setAttribute('stroke-dasharray', dash);

        for(var i=0; i<n; i++){ p=i/n_1;
            var sw=lerp(p, SW[0], SW[1]); // current stroke width
            var off=-i*dt; // current dash offset
            var c=toCss(lerpA(p, [255,0,0], [255,255,0])); // curr color

            var newP=p0.cloneNode(true);
            newP.setAttribute('style', 'stroke-width:'+sw+';stroke-dashoffset:'+off+';stroke:'+c);
            $(g).appendChild(newP);
        }
    }

    function f(){ $('abg').setAttribute('stroke', $('bg').getAttribute('fill')) }

//]]></script>
</svg>

enter image description here

enter image description here

Soleil
  • 6,404
  • 5
  • 41
  • 61
  • I have a question about tubefy. A cursory glance through explanation in the link you mentioned (and other info about tubefy online) suggests that it is about a gradient emanating from an arbitrary line / stroke. But the gradient itself seems uniform for each point of the line (if I take two points on the line a and b, then for points x and y on the perpendicular at tangent at the same distance from a and b will have the same intensity?). In that case, I don't think you could achieve variable width using it. – Peeyush Kushwaha Dec 01 '20 at 09:05
  • @PeeyushKushwaha A linear interpolation can also be seen as Taylor expansion of any (non constant) function, hence if you put enough many points and interpolate, you can an achieve arbitrary amount of variable width. Using an order 2 function could be more efficient in terms of space complexity (memory size, objects count). In the Tubefy link, it's used of color gradient using clones, but you can use it for width gradient as explained at the beginning of the Variable Stroke-Width page (http://owl3d.com/svg/vsw/articles/vsw_article.html). – Soleil Dec 01 '20 at 12:35
  • @PeeyushKushwaha Maybe you actually refered to *non-uniform* variable width; for that case, you keep the path as reference, and add several reference points for width, and you interpolate between them (with order 2 / bezier functions). This is not implemented in the vsw article, but it is not far. In a pixel based graphic and a tablet pen with pressure, it's exactly represented this way: many reference points (with a certain shape) are added as you draw, but there is no interpolation, only pixels being modified at a certain frequency. That would be your base type, common for pixels/vectors. – Soleil Dec 01 '20 at 13:26
  • 1
    Thanks a lot for the detailed information! I'm certainly gonna have to spend some more time understanding the tubefy one, but I believe one of these will lead to solving my problem. – Peeyush Kushwaha Dec 02 '20 at 06:51
1

Unfortunately this has been proposed but not further developed as an SVG standard:

https://www.w3.org/Graphics/SVG/WG/wiki/Proposals/Variable_width_stroke

Your best bet would be to generate your own outline curve based on the desired inner curve and stroke widths.

Adobe Illustrator does this when using their width tool, and Inkscape has a feature which does that too.

So technically to answer your question, the .ai file format does save stroke width information, but when exported to SVG it is a closed path with fill.

Manstie
  • 333
  • 1
  • 5
  • 13
  • `generate your own outline curve based on the desired inner curve and stroke widths` ah so you mean instead of an inner curve with variable stroke width, I use a 0 stroke width curve on both sides of the outline and then "fill" it? Is that what you're suggesting? And if so, then is it possible to fill the space between two curves? – Peeyush Kushwaha Dec 01 '20 at 09:09
  • Yes, with a fully defined outline path you can then fill it, e.g. `` – Manstie Dec 04 '20 at 02:38
  • ah I see, you're proposing using just one `path` instead of two for the outline. I agree that this would be a good low-level representation for an outline, however I expect that the calculation for what the path `d` should be (given the inputs in variable width stroke proposal) would be somewhat complicated. – Peeyush Kushwaha Dec 04 '20 at 05:57