Save inline SVG as JPEG/PNG/SVG

55,591

Solution 1

Nowadays this is pretty simple.

The basic idea is:

  1. SVG to canvas
  2. canvas to dataUrl
  3. trigger download from dataUrl

it actually works outside of the Stack Overflow snippet

var btn = document.querySelector('button');
var svg = document.querySelector('svg');
var canvas = document.querySelector('canvas');

function triggerDownload (imgURI) {
  var evt = new MouseEvent('click', {
    view: window,
    bubbles: false,
    cancelable: true
  });

  var a = document.createElement('a');
  a.setAttribute('download', 'MY_COOL_IMAGE.png');
  a.setAttribute('href', imgURI);
  a.setAttribute('target', '_blank');

  a.dispatchEvent(evt);
}

btn.addEventListener('click', function () {
  var canvas = document.getElementById('canvas');
  var ctx = canvas.getContext('2d');
  var data = (new XMLSerializer()).serializeToString(svg);
  var DOMURL = window.URL || window.webkitURL || window;

  var img = new Image();
  var svgBlob = new Blob([data], {type: 'image/svg+xml;charset=utf-8'});
  var url = DOMURL.createObjectURL(svgBlob);

  img.onload = function () {
    ctx.drawImage(img, 0, 0);
    DOMURL.revokeObjectURL(url);

    var imgURI = canvas
        .toDataURL('image/png')
        .replace('image/png', 'image/octet-stream');

    triggerDownload(imgURI);
  };

  img.src = url;
});
<button>svg to png</button>

<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="200" height="200">
  <rect x="10" y="10" width="50" height="50" />
  <text x="0" y="100">Look, i'm cool</text>
</svg>

<canvas id="canvas"></canvas>

Regarding the downloading part, you can set up a filename and etc etc (although not in this example). Some days ago I answered a question on how to download a specific portion of HTML from the given page. It might be useful regarding the downloading part: https://stackoverflow.com/a/28087280/2178180

update: now letting you specify the filename

Solution 2

Here's a solution that works in IE11 as well.

I just did a bunch of testing of various methods of this and while the above answer by Ciro Costa is fantastic in that it works in Firefox and Chrome it does not work in IE11. IE11 fails due to a security issue with rendering an svg to the canvas which requires a canvas implementation, canvg. Here's a solution using canvg that's pretty terse and works in the latest versions of Chrome, Firefox, Edge, and IE11.

Fiddle: https://jsfiddle.net/StefanValentin/9mudw0ts/

DOM

<svg
  id="my-svg"
  xmlns="http://www.w3.org/2000/svg"
  xmlns:xlink="http://www.w3.org/1999/xlink"
  version="1.1"
  width="200"
  height="200"
>
  <rect x="10" y="10" width="50" height="50" />
  <text x="0" y="100">Look, i'm cool</text>
</svg>

JavaScript

var svg = document.querySelector('#my-svg');
var data = (new XMLSerializer()).serializeToString(svg);
// We can just create a canvas element inline so you don't even need one on the DOM. Cool!
var canvas = document.createElement('canvas');
canvg(canvas, data, {
  renderCallback: function() {
    canvas.toBlob(function(blob) {
        download('MyImageName.png', blob);
    });
  }
});

The download function above could be whatever you want to do, as there are many ways to trigger a download via JavaScript. Here's the one we use that works in all the browsers I've tested.

// Initiate download of blob
function download(
  filename, // string
  blob // Blob
) {
  if (window.navigator.msSaveOrOpenBlob) {
    window.navigator.msSaveBlob(blob, filename);
  } else {
    const elem = window.document.createElement('a');
    elem.href = window.URL.createObjectURL(blob);
    elem.download = filename;
    document.body.appendChild(elem);
    elem.click();
    document.body.removeChild(elem);
  }
}

Solution 3

Working off @CiroCosta. 1 option if you are having trouble exporting an element you could just draw the image to the canvas before drawing the svg image

btn.addEventListener('click', function () {
  var canvas = document.getElementById('canvas');
  var ctx = canvas.getContext('2d');
  var data = (new XMLSerializer()).serializeToString(svg);
  var DOMURL = window.URL || window.webkitURL || window;

  // get the raw image from the DOM
  var rawImage = document.getElementById('yourimageID');
  var img = new Image();
  var svgBlob = new Blob([data], {type: 'image/svg+xml;charset=utf-8'});
  var url = DOMURL.createObjectURL(svgBlob);

  img.onload = function () {
    ctx.drawImage(rawImage, 0, 0);
    ctx.drawImage(img, 0, 0);
    DOMURL.revokeObjectURL(url);

    var imgURI = canvas
      .toDataURL('image/png')
      .replace('image/png', 'image/octet-stream');

    triggerDownload(imgURI);
  };

  img.src = url;
});

Worked for me but only for png and jpeg. SVG files still only display inline elements and not tags

EDIT: The way you create an svg like this is actually by converting the image tag into Base64 and the setting that as the xlink:href in the image attributes like this:

<image id="crop" width="725" height="1764" xlink:href="data:image/png;base64,iVBORw0KGgo ... " />

and then triggering the download on the whole svg url like this:

btn.addEventListener('click', function () {
  var canvas = document.getElementById('canvas');
  var ctx = canvas.getContext('2d');
  var data = (new XMLSerializer()).serializeToString(svg);
  var DOMURL = window.URL || window.webkitURL || window;

  var rawImage = document.getElementById('yourimageID');
  var img = new Image();
  var svgBlob = new Blob([data], {type: 'image/svg+xml;charset=utf-8'});
  var url = DOMURL.createObjectURL(svgBlob);

  img.onload = function () {
    ctx.drawImage(img, 0, 0);

    triggerDownload(url);
    DOMURL.revokeObjectURL(url);
  }
};

you can convert pngs like this here:

function getDataUri(url, callback) {
  var image = new Image();

  image.onload = function () {
    var canvas = document.createElement('canvas');
    canvas.width = this.naturalWidth; // or 'width' if you want a special/scaled size
    canvas.height = this.naturalHeight; // or 'height' if you want a special/scaled size

    canvas.getContext('2d').drawImage(this, 0, 0);

    // Get raw image data
    callback(canvas.toDataURL('image/png').replace(/^data:image\/(png|jpg);base64,/, ''));

    // ... or get as Data URI
    callback(canvas.toDataURL('image/png'));
  };

  image.src = url;
}

then setting the attribute

getDataUri('localImagepath', function (dataUri) {
  image.setAttribute('xlink:href', dataUri);
});

Solution 4

The solution for saving inline SVG as SVG file

Works in modern browsers

<svg width="100" height="100">
  <rect fill="red" x="0" y="0" width="100" height="100" />
</svg>

<button>Save to SVG</button>
let btn = document.querySelector('button')
let svg = document.querySelector('svg')

let triggerDownload = (imgURI, fileName) => {
    let a = document.createElement('a')

    a.setAttribute('download', 'image.svg')
    a.setAttribute('href', imgURI)
    a.setAttribute('target', '_blank')

    a.click()
}

let save = () => {
    let data = (new XMLSerializer()).serializeToString(svg)
    let svgBlob = new Blob([data], {type: 'image/svg+xml;charset=utf-8'})
    let url = URL.createObjectURL(svgBlob)

    triggerDownload(url)
}

let btn = document.querySelector('button')
btn.addEventListener('click', save)

Codepen: https://codepen.io/udovichenko/pen/yLXaWLB

Share:
55,591
Tim Rideyourbike
Author by

Tim Rideyourbike

Updated on July 09, 2022

Comments

  • Tim Rideyourbike
    Tim Rideyourbike almost 2 years

    I have an inline SVG in my html, and I need to be able to save this as either a JPEG, PNG or SVG.

    I have tried a few different methods with converting the SVG to canvas and then converting to JPEG, but I haven't been able to get these working.

    Here is an example of my inline SVG.

    .font {
    	color: #ffffff;
    	font-family: Roboto;
    	font-weight: bold;
    	text-transform: uppercase;
    }
    .name {
    	font-size: 64pt;
    }
    .top-bar-text {
    	font-size: 32pt;
    }
    .font tspan {
    	dominant-baseline: middle;
    }
    <link href='http://fonts.googleapis.com/css?family=Roboto:700' rel='stylesheet' type='text/css'>
    
    <svg width="256" height="256" id="icon">
      <rect class="bg1" id="bg_color_1" x="0" y="0" width="256" height="256" fill="#4cbc5a" />
      <path class="bg2" id="bg_color_2" d="M 0 96 L0,256 L256,256 L256,96 s -128 96 -256 0" fill="#08a21c" />
      <text id="left_corner_text" x="24" y="36" width="48" height="64" class="top_bar lct font top-bar-text" text-anchor="middle" fill="#ffffff"><tspan>1</tspan></text>
      <text id="right_corner_text" x="232" y="36" width="48" height="64" class="top_bar rct font top-bar-text" text-anchor="middle" fill="#ffffff"><tspan>2</tspan></text>
      <text id="line_1_text" transform="scale(1,2)" x="128" y="90" width="256" height="192" class="l1t font name" text-anchor="middle" fill="#ffffff"><tspan>ABC</tspan></text>
    </svg>

    Also, not all the elements need to be exported, as some of the options the user has is to remove the top corner numbers.

    I would like for when it's been converted to download straight to the browser.

  • WebbySmart
    WebbySmart almost 9 years
    How does this work? I copy the exact code and i cannot replicate it. Can you put it in jsfiddle?
  • WebbySmart
    WebbySmart almost 9 years
    Figured it out. The <script></script> portion needs to come after the <canvas> and all of the code. Before closing body tag.
  • WebbySmart
    WebbySmart almost 9 years
    Sorry for another response - is there any reason why this does not work in IE or FireFox?
  • Ciro Costa
    Ciro Costa almost 9 years
    Hi! Support in IE is hard baceuse it does not support the Blob api - see caniuse blob. I also think that the MouseEvent interface is not properly set (just guessing). Anyway, it should work well in Firefox. In which version have you tried? Here's a jsfiddle: jsfiddle.net/LznLjxq7 .
  • WebbySmart
    WebbySmart almost 9 years
    Thanks. The jsfiddle version you provided works in firefox. I.E. loses again. Thanks for your help.
  • code-sushi
    code-sushi about 8 years
    The jsfiddle version does not seem to work for me in Chrome or Safari. Firefox is fine, but it's not really a solution if it only works in one browser. What is the reason for this, and can anyone offer any insights as to why, or what might be done, to remedy the situation? thanks.
  • Ashith
    Ashith over 7 years
    This is kind of interesting… the reason I was searching for this and why i thought it was reasonable to expect a browser to have built in options for saving/printing/exporting inline SVG documents is because existing native options already exist for the canvas element. So while it makes perfect sense that converting the SVG to canvas would work as a way to get those options, my real question is : "shouldn't similar options already be built in to the browser for embedded SVGs?" Is there a reason to not expect similar functionality between the two in this context?
  • ptkato
    ptkato about 7 years
    I have a <image> tag inside the svg, but only the lines/paths get downloaded, do you have any clue?
  • SumNeuron
    SumNeuron about 7 years
    How can you make this a pdf?
  • Ankur Vyas
    Ankur Vyas almost 7 years
    @CiroCosta This is not working in iPhone any idea why? img.onload method is not trigger in iPhone.
  • Akbar Basha
    Akbar Basha over 6 years
    your sample is not working in IE and edge browser.. how to achieve this? @CiroCosta
  • daniel
    daniel over 6 years
    I have the same Problem as Patrick. Images are not rendered and also texts have the wrong font. Any solution found?
  • chandramouli.jamadagni
    chandramouli.jamadagni over 6 years
    How to change sizes when converting it to PNG. Need to accommodate the size changes for my website. Please do help me.
  • Admin
    Admin about 5 years
    This is a great answer, but in order to have the end image have the same sizing as the svg, make your canvas have the html(not css) attributes height and width. Its probably a good idea to do this onClick and make it grab the dimensions of the svg.
  • Muganwas
    Muganwas over 4 years
    Where is canvg() defined?
  • handle
    handle over 4 years
  • handle
    handle over 4 years
    ... if svg tag attributes width and height are not defined. So the snippet works fine.
  • Matt Schuette
    Matt Schuette over 4 years
    Safari doesn't seem to like the ;charset=utf-8 bit on the Blob type. This causes the img.onload not called problems. For sizing, I size my canvas and then also provide width and height params to the drawImage(...) call.
  • KhalilRavanna
    KhalilRavanna over 4 years
    In the JSFiddle if you check the resources you'll see canvg.min imported. That exposes canvg as a global. If you look at the docs though you can import it via npm and reference it by importing it: github.com/canvg/canvg
  • Matt Lightbourn
    Matt Lightbourn about 4 years
    This is excellent, I tried adding my own svg element which showed on screen in js.fiddle but when I click the button, only some of the svg ended up in a png and in really bad resolution and quality. Are there any updates to this excellent post? Thanks