Search code examples
svgtransparencyfabricjsalpha

How do I make fabric.js output svg including alpha color?


I need to output the result of a fabric canvas drawing to an SVG file.

I'm using fabric.js version 1.7.6 and when I have a path drawn to a canvas with an rgba fill like rgba(255,0,0,.15) the resulting SVG has a fill of rgb(0,0,0). Is there some setting I need to enable to make it output the alpha chanel?

In my sample code the purple circle converts to SVG properly, but the rectangle just shows up as black.

Sample HTML:

<html>
  <head>
    <script src="fabric.js"></script>
  </head>
  <body>

    <div id="canvasHolder" style="border: 3px solid black;">
      <canvas id="canvasElement" width="400" height="400" />
    </div>

    <div id="svgHolder" style="border: 3px solid blue;">
    </div>
  </body>

  <script>
    var canvas = new fabric.Canvas('canvasElement');

    var rect = new fabric.Path('M,0,0,h,100,v,100,h,-100,z',{
        top:100,
        left:100,
        stroke: 'green',
        fill: 'rgba(255,0,0,.15)'
    });
    canvas.add(rect);

    var circ = new fabric.Circle({
        radius: 30,
        top:30,
        left:30,
        stroke: 'blue',
        fill: 'purple'
    });
    canvas.add(circ);

    canvas.renderAll();

    // Make an SVG object out of the fabric canvas
    var SVG = canvas.toSVG();
   document.getElementById('svgHolder').innerHTML = SVG;
  </script>

</html>

Output SVG:

<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="400" height="400" viewBox="0 0 400 400" xml:space="preserve">
<desc>Created with Fabric.js 1.7.6</desc>
<defs>
</defs>
<path d="M 0 0 h 100 v 100 h -100 z" style="stroke: rgb(0,128,0); stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(0,0,0); fill-rule: nonzero; opacity: 1;" transform="translate(150.5 150.5) translate(-50, -50) " stroke-linecap="round"></path>
</svg>

Solution

  • As I said in comment, this looks like a bug, that you should report on the project's issue tracker.

    Colors are all converted to rgb() (rgba, hsl, hsla, hex, keywords) and thus don't support alpha channel...

    For the time being, here is an heavy workaround :

    toSVG accepts an reviver function, which will receive all the svg nodes markups. From there, you can reapply the correct styles, but not so easily.

    The only parameter I could find allowing us to identify which object corresponds to the svg markup we get, is the id one.

    • So first, we will construct a dictionary, which will store our colors, by id.
    • Then, we will assign the id and colors to our fabric's objects.
    • Finally, in the reviver, we will parse our markup to convert it to an svg node, check its id atribute, and then change its style.fill and style.stroke properties, before returning the serialization of this modified node.

    var canvas = new fabric.Canvas('canvasElement');
    
    var colors_dict = {};
    // an helper function to generate and store our colors objects
    function getColorId(fill, stroke) {
      var id = 'c_' + Math.random() * 10e16;
      return colors_dict[id] = {
        id: id,
        fill: fill || 'black',
        stroke: stroke || 'black' // weirdly fabric doesn't support 'none'
      }
    }
    
    // first ask for the color object of the rectangle
    var rect_color = getColorId('hsla(120, 50%, 50%, .5)', 'rgba(0,0,255, .25)');
    var rect = new fabric.Path('M,0,0,h,100,v,100,h,-100,z', {
      top: 60,
      left: 60,
      stroke: rect_color.stroke, // set the required stroke
      fill: rect_color.fill, // fill
      id: rect_color.id  // and most importantly, the id
    });
    canvas.add(rect);
    
    var circ_color = getColorId('rgba(200, 0,200, .7)');
    var circ = new fabric.Circle({
      radius: 30,
      top: 30,
      left: 30,
      stroke: circ_color.stroke,
      fill: circ_color.fill,
      id: circ_color.id
    });
    canvas.add(circ);
    
    canvas.renderAll();
    
    var parser = new DOMParser();
    var serializer = new XMLSerializer();
    function reviveColors(svg){
      // first we parse the markup we get, and extract the node we're interested in
      var svg_doc = parser.parseFromString('<svg xmlns="http://www.w3.org/2000/svg">' + svg + '</svg>', 'image/svg+xml');
      var svg_node = svg_doc.documentElement.firstElementChild;
      var id = svg_node.getAttribute('id');
      if (id && id in colors_dict) { // is this one of the colored nodes
        var col = colors_dict[id]; // get back our color object
        svg_node.style.fill = col.fill; // reapply the correct styles
        svg_node.style.stroke = col.stroke;
        // return the new markup
        return serializer.serializeToString(svg_node).replace('xmlns="http://www.w3.org/2000/svg', '');
      }
      return svg;
    }
    
    // Make an SVG object out of the fabric canvas
    var SVG = canvas.toSVG(null, reviveColors);
    document.getElementById('svgHolder').innerHTML = SVG;
    <script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.12/fabric.js"></script>
    <div id="canvasHolder" style="border: 3px solid black;">
    <!-- beware canvas tag can't be self-closing -->
    <canvas id="canvasElement" width="400" height="200"></canvas>
    </div>
    <div id="svgHolder" style="border: 3px solid blue;">