Search code examples
vector-graphics

Support for variable thickness stroke


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?


Solution

  • 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