Saving Browser-based SVGs as Images

svg-to-png

I’ve spent a lot of time with D3 over the past several months, and while I’ve enjoyed it immensely, one thing had eluded me: saving visualizations as images. When I needed to turn a browser-drawn SVG (scalable vector graphic) into an image, I used FileSaver.js to save the content of the webpage’s SVG node as a file, then opened the file in Inkscape and rasterized to an image format. Unfortunately, this process often created discrepancies between how the visualization looked in the browser and how it looked in the final image.

While making a calendar as a gift over the holidays, I finally figured out how to save SVG content as an image directly from the browser. Not only is the workflow a lot smoother, but the results are much more consistent than processing the SVG through Inkscape. The core code for the process is laid out below, and the complete script is on GitHub.

1. Get the SVG Code

Saving a browser-drawn SVG as an image requires several steps, but they’re all reasonably straight-forward. After generating the visualization, we can base-64 encode the SVG and build a data URI to use as the source of an image element. The image, in turn, can be drawn to a canvas, from which the data URI for a PNG image can be generated and a PNG file downloaded.

Having drawn an SVG in the browser, whether by hand, with D3, or using some other library, we need the XML code from the SVG element. While browser support for the .innerHTML attribute is common, what we really need is the much less commonly supported .outerHTML attribute, which includes the HTML code of the SVG element itself. We can use .innerHTML to get the outer HTML of a given element by cloning the node and adding it to another element:

function outerHTML(el) {
  var outer = document.createElement('div');
  outer.appendChild(el.cloneNode(true));
  return outer.innerHTML;
}

If we save the SVG code at this point, we won’t have any of the styling defined in stylesheets. We need to include the CSS styles from the webpage. It is possible to inline the styles on the individual elements, but SVGs also allow a <style> tag where CSS rules can be defined. I prefer to loop through all the CSS rules on the page and include only the ones with matching elements.

function styles(dom) {
  var used = "";
  var sheets = document.styleSheets;
  for (var i = 0; i < sheets.length; i++) {
    var rules = sheets[i].cssRules;
    for (var j = 0; j < rules.length; j++) {
      var rule = rules[j];
      if (typeof(rule.style) != "undefined") {
        var elems = dom.querySelectorAll(rule.selectorText);
        if (elems.length > 0) {
          used += rule.selectorText + " { " + rule.style.cssText + " }\n";
        }
      }
    }
  }

  var s = document.createElement('style');
  s.setAttribute('type', 'text/css');
  s.innerHTML = "

The styles are wrapped in the CDATA construct so any angle brackets in the CSS code do not confuse the XML parser.

2. Add Some Boilerplate

With your page’s styles included, you can save the SVG to a file and open it in Inkscape with decent results. However, to load the SVG as a browser image we need to add the XML version and doctype. First, prepend the following:


;

Then add the version, xmlns, and xmlns:xlink attributes to the SVG node.

function setAttributes(el) {
  el.setAttribute("version", "1.1");
  el.setAttribute("xmlns", "http://www.w3.org/2000/svg");
  el.setAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink");
}

3. Convert to a Data URI

Mike Bostock’s instructions for creating an SVG fallback image shows how to use a saved SVG file to download a PNG image. Rather than saving an SVG file only to load it back into the browser, we can create a data URI of the SVG and use it as the source for the image instead.

function svgImage(xml) {
  var image = new Image();
  image.src = 'data:image/svg+xml;base64,' + window.btoa(xml);
}

To support Unicode characters, we need to amend the above snippet to do a bit more work:

window.btoa(unescape(encodeURIComponent(xml)))

4. Convert to an Image

To save an image file from our image tag, we need to create a canvas of the right size, draw the image to it, get a data URI of the canvas’s contents, then set the data URI as the destination of a link and click the link.

image.onload = function() {
  var canvas = document.createElement('canvas');
  canvas.width = image.width;
  canvas.height = image.height;
  var context = canvas.getContext('2d');
  context.drawImage(image, 0, 0);

  var a = document.createElement('a');
  a.download = "image.png";
  a.href = canvas.toDataURL('image/png');
  document.body.appendChild(a);
  a.click();
}

Celebrate!

There you have it! Put all the steps together, and we can download an SVG drawn in the browser as a PNG image. To rasterize the SVG to a higher resolution, we can scale the SVG node’s width and height attributes and wrap the contents of the SVG node in a <g> element with a transform attribute to scale the drawing by some multiplier.

One stumbling block I discovered is the use of images within the SVG. To include them in the rasterized image, they need to be inlined as data URIs. Even if you only want to save the SVG code as an SVG file, it can be helpful to inline images using data URIs as it makes the SVG self-contained rather than relying on external files.

The full, functioning script is on GitHub. You can drop it into a webpage, call the saveSvgAsPng function, and it will download an image of the SVG you see in your browser. The script is the product of my own tinkering, and far from universally tested, so if you run into problems, be sure to let me know.

If you have any other tricks for SVG-to-PNG conversion or otherwise getting SVGs out of the browser, I’d love to hear them!
 

Conversation
  • Sip says:

    Only left corner part of the chart is coming when i am trying for svg export
    here is my code:

    saveSvgAsPng(document.getElementById(“chart-svg”), “diagram.png”, 3);

    • Manfred says:

      I have the same Problems like Perry Pierce:
      I am very new to this and am attempting to use your solution with a d3.js graph. I am not having any success and believe it may be an issue of providing the correct elementID when calling the function. The ID I am passing is the id given to the div where the graph is displayed. Has anyone used with a D3 graph and if so what elementId did you use?
      Specielly with
      https://github.com/andredumas/techan.js
      Becaus, there are any indicator? Have anyone a Example for me.

  • Kimberly Nicholls says:

    Thanks, this was very helpful. I found all sorts of much more complicated ways to do it, so I really like the simplicity.

  • Amirali says:

    Looks great, but could not get it to work in my case. I have some linked images in my SVG code (png and svg files, less than 50KB) and your code seems to stop recreating them in canvas. I have also used markers on my paths. is it able to reproduce those as well?

    Thanks again.

    • Eric Shull says:

      It’s true: the code above doesn’t go into being able to save images in the SVG, but if you look at the GitHub repo (specifically here: https://github.com/exupero/saveSvgAsPng/blob/gh-pages/saveSvgAsPng.js#L6-L30) there’s some code for inlining images that you can use before putting the SVG on a canvas an exporting as a PNG. I can’t guarantee that it works in all cases, but it’s worked for me so far.

      The only path markers I’ve tried exporting are end markers, but those worked fine. If you’re seeing something that isn’t exported correctly, feel free to file an issue on GitHub (https://github.com/exupero/saveSvgAsPng/issues?state=open) with some sample SVG code.

      • Amirali says:

        Thanks for the reply.
        I was actually talking about your latest script on github, updated 3 days ago. I now tried it with multiple linked png and svg images. what I can see in the png outcome is only the enlarged linked png that is used inside the svg code and not the rest. here is my svg:

        text

        text

        The 2 linked images are here:
        png : http://i.imgur.com/bV5qrz3.png
        svg: http://upload.wikimedia.org/wikipedia/commons/3/30/Vector-based_example.svg

        • Eric Shull says:

          It’s possible I’ve broken that functionality. I’m in the process of adding tests to make sure the script doesn’t break. If you could, create an issue on the GitHub repository with your example code (especially since example code doesn’t appear in our blog’s comments).

      • Amirali says:

        Apparently the code vanished from the comment. here it is:
        http://jsfiddle.net/vYZ3y/2/

        I realized that saving svg with online image sources linked, will result in a security error by your script. therefore please try it with offline copies.

  • Peter says:

    I downloaded your script and tried it, but I’ve got a problem… Chrome says:

    Uncaught SyntaxError: Unexpected token ILLEGAL saveSvgAsPng.js:1

    If I click on saveSvgAsPng.js:1, Chrome does show me lots of Chinese characters.

  • David says:

    Thanks for this. I’ve just spent a long time working on a bug though. Turns out that sending an SVG with an xmlns attribute will do nothing. I commented out lines:

    //clone.setAttributeNS(xmlns, “xmlns”, “http://www.w3.org/2000/svg”);
    //clone.setAttributeNS(xmlns, “xmlns:xlink”, “http://www.w3.org/1999/xlink”);

    And now it works.

  • edward valadez says:

    Is there a way to save the .png file with a relative or an absolute path, without the need for open a window?
    Thanks in advice for the replies.
    And great work for the plugin ;)

    • edward valadez says:

      I found a solution :D!
      If someone want the code that I used feel free for ask.
      Also, if there is a better way than my code let us know on the replies.
      Thanks in advice.

      • pravin says:

        can you post the code for converting scg to png in c#

        • Eric Shull says:

          This post demonstrates a browser-based solution, and therefore can only be done with JavaScript. You’ll need to use another technique to convert SVGs to PNGs with C#.

      • pravin says:

        can you post the code for converting svg to png in c#

  • Lenny Turetsky says:

    Thank you so much for making this! It’s incredibly helpful, and solves my problem better than canvg (lack of CSS support, tho it handles embedded images).

    However, I found that some of my CSS wasn’t getting applied. It seems that it can’t handle:
    1) @font-face {...} – an easy change to the code around https://github.com/exupero/saveSvgAsPng/blob/gh-pages/saveSvgAsPng.js#L63
    2) CSS rules where my SVG isn’t at the top level, such as body div svg text.foo {...} because once the rule.selectorText needs to remove the body div prefix in order to work correctly. (I solved this by passing in a selectorRemap function, but it’s not a general purpose fix so I didn’t make a pull-request.)

    Thanks again for this wonderfully helpful code!

  • maninder says:

    Thanks for all the details above.

    I am trying to save an SVG into image and it work well in most of the cases but it fails when there is a non-English characters in my SVG for example for one of my chart I am getting the label as ‘Zamówienia’.

    Second, How will the above script works when we have some images as part of the SVG.

  • maninder says:

    I got the solution for the first problem, can any one help for the second one.

    I have a image as a part of my SVG and wanted to save the SVG as image.

  • Eric Shull says:

    Your images need to be inlined using data URIs within the SVG document or on the same host as the SVG document. I don’t believe images from external hosts can be included. The developer console may have more information.

    If you find a bug, please report it to the GitHub project: https://github.com/exupero/saveSvgAsPng/issues

  • maninder says:

    HI Eric.

    Thanks for the response.

    Currently I am working on an application which generates charts and my users take the iFrame of my chart and use it in their website. Now I want to allow the user to save the chart as an image.

    Can you help me to find the possible solution for this.

    • Eric Shull says:

      At a high level, the script does the following:

      1. Get the XML content of the SVG.
      2. Turn it into an SVG data URI.
      3. Create an image and set the data URI as its source.
      4. Get the image on the canvas as a png data URI.
      5. Create a link with the href set to the png data URI.
      6. Give the link a download name so clicking it downloads to the user’s filesystem.
      7. Add the link to the DOM and click it.

      I don’t know much about iframes, so I don’t know where this process breaks down when using them, but wherever the problem is, you should be able to modify this process to do what you want.

  • maninder says:

    HI Eric,

    Thanks again. One more point, when you say ‘Your images need to be inlined using data URIs within the SVG document ‘.

    What does it actually mean.

    • Eric Shull says:

      Any images referenced by URL in the SVG need to be included using data URIs instead. A data URI is a base64-encoded version of an image that doesn’t require loading anything outside the SVG, and can be used as an image’s src attribute.

  • maninder says:

    thanks Eric

  • Gaurav Kumar says:

    Thanks Eric.

    Your solution works perfectly in Chrome and Firefox. It’s not working in IE9 and Higher version.

    We are able to see the preview of the image but save image functionality is not working.

    • Eric Shull says:

      Gaurav,

      There’s a known problem with IE. It doesn’t support getting the image off a canvas when other content has been loaded onto it. It throws a security error when you try, which is why the image fails to download. There’s nothing I can do about it, but if you find a solution, please let me know.

    • Tom Wayson says:

      Gaurav

      You may not need this for IE9+. At least in IE10 (and IE9) emulated mode, I can right-click on SVG charts and sele

      • Tom Wayson says:

        sorry, keystroke fail, anyway, right-click and select “Save Picture As…” and a file save dialog shows up w/ SVG and PNG file fomat options.

        I haven’t tested this too much, but in the few cases that I have, the PNG was pretty close to the chart SVG. Your mileage may vary.

  • vic says:

    This script looks like it does just the trick, except I lose my axis labels when going to png. Is this a known issue and is there a known fix?

  • kc says:

    If you are using em/rem for font sizing then the labels become much smaller, almost invisible. Change the em/rem to respective px vlaue and it will work

  • Yannik says:

    I’ve had the same frustration when I wanted to share my graphs in Powerpoint or emails. However, I did not want to touch my code (sometimes embed in other frameworks etc…). So, I came up with a different solutions:

    First, I’m using the awesome NYT’s bookmarklet SVG Crowbar to get the SVG from a page.
    Optionally, I modify the SVG in Illustrator.
    Then, I have created a page from which I can easily drop my SVGs and the drag the image somewhere else. You can check it out at http://myre.ch/svg2png/.

    Typically, I would have the page open all day long in the back of my desktop. Hope it will help someone.

  • Perry Pierce says:

    I am very new to this and am attempting to use your solution with a d3.js graph. I am not having any success and believe it may be an issue of providing the correct elementID when calling the function. The ID I am passing is the id given to the div where the graph is displayed. Has anyone used with a D3 graph and if so what elementId did you use?

  • Wim Imans says:

    Thanks for this, Eric, it works great!

    Regarding the IE security error, I like the simple suggestion by Tom Wayson. It’s just an idea, but maybe you can add some code to include a pop-up message whenever the code is triggered by an IE-user? With a message like “In IE, right-click the image and save as…”.

    I know it’s quick and dirty, not an actual fix, and it’s far from elegant, but at least it would catch the error and direct a user in the right direction.

    Thanks again for this nice solution!

  • Pavel says:

    Hi Eric,

    I’m trying your example, however I got error both in Firefox and Chromium.

    Firefox gives error:
    NS_ERROR_NOT_AVAILABLE: context.drawImage(image, 0, 0); graph_export.js (line 94)

    Chromium: Uncaught Error: SecurityError: DOM Exception 18 graph_export.js:98 image.onload

    I think both error are caused by the fact the browser prevents cross-site scripting, see http://getcontext.net/read/chrome-securityerror-dom-exception-18

  • Pavel says:

    Hi,
    I didn’t used your project “saveSvgAsPng”, I just took your steps and applied them in my environment.
    I’m pointing the fact, that your solution cannot work in modern browsers due to cross-site-scripting protection. See the link http://getcontext.net/read/chrome-securityerror-dom-exception-18

    • Eric Shull says:

      This solution doesn’t require any cross-site scripting. It operates fully within the browser page and downloads the image to the user’s filesystem. No other websites should be involved.

      If the solution in this blog post isn’t working for you, please leave a comment on the Github repo as it will be easier to track, manage, and fix the problem than by using the comments section of this blog post.

  • Joe Fraser says:

    Eric’s code gives the fundamentals of building the png. But, I have found in practice that I have to trim(remove the padding on the edges) from the png image. In my case I also had to combine png images after the svg to png conversion. I accomplished this on the server using java code. Can I do the same on the client. Does a java BufferedImage exist in javascript???

    Joe

    • Eric Shull says:

      Hi Joe. I’ve never seen padding around the image that wasn’t part of the original SVG. Could you put an example on the GitHub repo (https://github.com/exupero/saveSvgAsPng/issues)?

      You can combine images together on the client using the canvas element. Look at the above `image.onload` code for an example of drawing to a canvas and getting the composite image as a data URI.

  • Aravind says:

    Worked like a charm, exactly what I wanted. Thanks for your efforts.

  • Amahna says:

    Eric,

    Thanks for sharing the solution ! It works great on FireFox for an svg containing an image element. However, the solution doesn’t work in Chrome. There is no error from the script but the download dialog box does not open up. Any clues as to what could be causing this issue ??

    Thanks

  • Arun Joshi says:

    Working at all the browsers except the Edge browser.

    Could you help me in this.

    I have written a code that converts SVG to canvas Elements using canvg and then convert it to PNG Images. This is working at everywhere even IE11 also but not on the EDGE.

    • Eric Shull says:

      Sorry for the late reply. Our blog system had a problem with comments that just got fixed.

      I’m glad to hear it’s working for you in IE11. I can’t currently test Edge due to being on a Mac, but if you’d like to leave a bug report on the GitHub repo, someone might be able to offer a fix. Here’s the link: https://github.com/exupero/saveSvgAsPng/issues

  • min says:

    Hi Eric, I need your help. I have been trying to solve this problem over 2 weeks now. I want to save d3 results as images.
    None of tips and solutions are working with my code. Either I wrote very weird programs or I just need help to use your solution. So far your solution is the most efficient in my opinion.
    Would you help me?

    This is the code that I am building
    http://minstersinc.com/d3/exampledownload.html

    I would like to save the end result in any image file.

    I am really stuck.
    I can send you my code and other related files.
    please please let me know

  • min says:

    Hi Eric,

    I really need your help. I am trying to make an website with d3.
    It has a dynamic feature (drawing lines, add nodes, etc.)
    and I cannot figure to save the end result out as a image.
    I tried many different examples but it did not success.
    Would you please help me out??
    thank you
    min

  • yura says:

    Hi,

    Your code is the best solution I’ve found to convert svg charts to png.However, I’m struggling to find a way to modify this code in order to get the png files and send them on server side through an Ajax post request (without downloading them on the client device).
    Indeed, I need to get the png images of several nvd3 charts on the server in order to include them as parameters of an html to pdf converting function.

    As I’m pretty new to javascript, I’m not sure how to proceed. Would you have any suggestion?

    Thank you,

    • Eric Shull says:

      To send the image to the server instead of downloading it, change the code that creates a link to the image and clicks to download it. Use the base64-encoded PNG from the canvas in your POST request.

      • yura says:

        Thank for your answer, then I see I need the result of cb function in svgAsPngUri (on the github project), which begins by “data:image/png;base64,…”
        But is there a way to get this result outside of the callback function? I tried with a global var and a setTimeout but nothing helps.

        • Eric Shull says:

          There’s not a good way to get the URI outside the callback function, but you should be able to do everything you need from within the callback. This is common in Javascript, and you’ll quickly encounter other tools that work the same way.

          • yura says:

            Ok, I will try to deal with this configuration then.
            Thank you for your quick answers Eric.

  • Antonio says:

    hi Eric
    There is a problem regarding your code.
    It works well in chrome but it doesn’t work in safari.
    Can you help me?

  • Comments are closed.