23

I am trying to convert an external svg icon to a base64 png using a canvas. It is working in all browsers except Firefox, which throws an error "NS_ERROR_NOT_AVAILABLE".

var img = new Image();
img.src = "icon.svg";

img.onload = function() {
    var canvas = document.createElement("canvas");              
    canvas.width = this.width;
    canvas.height = this.height;
    var ctx = canvas.getContext("2d");
    ctx.drawImage(this, 0, 0);
    var dataURL = canvas.toDataURL("image/png");
    return dataURL;
};

Can anyone help me on this please? Thanks in advance.

ns16
  • 1,322
  • 2
  • 17
  • 26
Aneesh
  • 1,193
  • 3
  • 16
  • 26
  • 1
    Does your svg icon have width and height attributes? If it does are they percentages? – Robert Longson Feb 24 '15 at 08:02
  • Hi Robert, this is a svg file not a svg dom element and we can assign any width/height to it. I am using this svg in the page as – Aneesh Feb 24 '15 at 09:33
  • That doesn't answer my question. Does icon.svg have width/height attributes on the root `` element. If it does are those attribute values percentages? – Robert Longson Feb 24 '15 at 09:34
  • The svg icon is generated using Adobe Illustrator and I dont see any width/height in the svg file. – Aneesh Feb 24 '15 at 09:46
  • 4
    I had the same problem except that the call to `drawImage()` didn't even throw an error. Adding width/height in the did fix it! – Eldritch Conundrum Jul 07 '16 at 22:10

3 Answers3

59

Firefox does not support drawing SVG images to canvas unless the svg file has width/height attributes on the root <svg> element and those width/height attributes are not percentages. This is a longstanding bug.

You will need to edit the icon.svg file so it meets the above criteria.

Robert Longson
  • 118,664
  • 26
  • 252
  • 242
6

As mentioned, this is an open bug caused by limitations on what Firefox accepts as specification for SVG sizes when drawing to a canvas. There is a workaround.

Firefox requires explicit width and height attributes in the SVG itself. We can add these by getting the SVG as XML and modifying it.

var img = new Image();
var src = "icon.svg";

// request the XML of your svg file
var request = new XMLHttpRequest();
request.open('GET', src, true)

request.onload = function() {
    // once the request returns, parse the response and get the SVG
    var parser = new DOMParser();
    var result = parser.parseFromString(request.responseText, 'text/xml');
    var inlineSVG = result.getElementsByTagName("svg")[0];
    
    // add the attributes Firefox needs. These should be absolute values, not relative
    inlineSVG.setAttribute('width', '48px');
    inlineSVG.setAttribute('height', '48px');
    
    // convert the SVG to a data uri
    var svg64 = btoa(new XMLSerializer().serializeToString(inlineSVG));
    var image64 = 'data:image/svg+xml;base64,' + svg64;
    
    // set that as your image source
    img.src = img64;

    // do your canvas work
    img.onload = function() {
        var canvas = document.createElement("canvas");              
        canvas.width = this.width;
        canvas.height = this.height;
        var ctx = canvas.getContext("2d");
        ctx.drawImage(this, 0, 0);
        var dataURL = canvas.toDataURL("image/png");
        return dataURL;
    };
}
// send the request
request.send();

This is the most basic version of this solution, and includes no handling for errors when retrieving the XML. Better error handling is demonstrated in this inline-svg handler (circa line 110) from which I derived part of this method.

  • This is a really good answer, and it worked for my implementation for most SVGs. To improve its compatibility, I replaced the `btoa(new XMLSerializer().serializeToString(inlineSVG));` line with `Buffer.Buffer.from(new XMLSerializer().serializeToString(inlineSVG), 'utf').toString('base64');` (Using the standalone NodeJS Buffer plugin, which can be downloaded here: [https://bundle.run/buffer@6.0.3](https://bundle.run/buffer@6.0.3) – Milun May 20 '22 at 03:40
  • http://phrogz.net/SVG/svg_to_png.xhtml is different in Firefox, Chrome, and Safari, so you really need to have control of the SVG file, or use the method in this answer. – forresto Sep 22 '22 at 06:27
0

This isn't the most robust solution, but this hack worked for our purposes. Extract viewBox data and use these dimensions for the width/height attributes.

This only works if the first viewBox encountered has a size that accurately can represent the size of the SVG document, which will not be true for all cases.

   // @svgDoc is some SVG document.
   let svgSize = getSvgViewBox(svgDoc);

   // No SVG size?
   if (!svgSize.width || !svgSize.height) {
      console.log('Image is missing width or height');

   // Have size, resolve with new SVG image data.
   } else {
      // Rewrite SVG doc
      let unit = 'px';
      $('svg', svgDoc).attr('width', svgSize.width + unit);
      $('svg', svgDoc).attr('height', svgSize.height + unit);

      // Get data URL for new SVG.
      let svgDataUrl = svgDocToDataURL(svgDoc);
   }


function getSvgViewBox(svgDoc) {
   if (svgDoc) {
      // Get viewBox from SVG doc.
      let viewBox = $(svgDoc).find('svg').prop('viewBox').baseVal;

      // Have viewBox?
      if (viewBox) {
         return {
            width: viewBox.width,
            height: viewBox.height
         }
      }
   }

   // If here, no viewBox found so return null case.
   return {
      width: null,
      height: null
   }
}

function svgDocToDataURL(svgDoc, base64) {
   // Set SVG prefix.
   const svgPrefix = "data:image/svg+xml;";

   // Serialize SVG doc.
   var svgData = new XMLSerializer().serializeToString(svgDoc);

   // Base64? Return Base64-encoding for data URL.
   if (base64) {
      var base64Data = btoa(svgData);
      return svgPrefix + "base64," + base64Data;

   // Nope, not Base64. Return URL-encoding for data URL.
   } else {
      var urlData = encodeURIComponent(svgData);
      return svgPrefix + "charset=utf8," + urlData;
   }
}
Crashalot
  • 33,605
  • 61
  • 269
  • 439