Reputation: 707
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.
Upvotes: 52
Views: 76168
Reputation: 2585
Nowadays this is pretty simple.
The basic idea is:
it actually works outside of the Stack Overflow snippet
function triggerDownload(imgURI) {
const a = document.createElement('a');
a.download = 'MY_COOL_IMAGE.png'; // filename
a.target = '_blank';
a.href = imgURI;
// trigger download button
// (set `bubbles` to false here.
// or just `a.click()` if you don't care about bubbling)
a.dispatchEvent(new MouseEvent('click', {
view: window,
bubbles: false,
cancelable: true
}));
}
const btn = document.querySelector('button');
btn.addEventListener('click', function () {
const svgNode = document.querySelector('svg');
const svgString = (new XMLSerializer()).serializeToString(svgNode);
const svgBlob = new Blob([svgString], {
type: 'image/svg+xml;charset=utf-8'
});
const DOMURL = window.URL || window.webkitURL || window;
const url = DOMURL.createObjectURL(svgBlob);
const image = new Image();
image.width = svgNode.width.baseVal.value;
image.height = svgNode.height.baseVal.value;
image.src = url;
image.onload = function () {
const canvas = document.getElementById('canvas');
canvas.width = image.width;
canvas.height = image.height;
const ctx = canvas.getContext('2d');
ctx.drawImage(image, 0, 0);
DOMURL.revokeObjectURL(url);
const imgURI = canvas
.toDataURL('image/png')
.replace('image/png', 'image/octet-stream');
triggerDownload(imgURI);
};
});
<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, image width and height and etc etc (although mostly not included 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 2021-11-30: now letting you specify the filename
update 2023-06-29: improve code readability and ensure image width and height match the SVG.
Upvotes: 93
Reputation: 342
In general, the solutions presented here work, but don't forget to explicitly set the canvas size in pixels, otherwise the image may be cropped. For example:
// get the size of the svg image
const { width, height } = svg.getBBox();
// create a canvas and set its size
var canvas = document.createElement(`canvas`);
canvas.setAttribute(`width`, width);
canvas.setAttribute(`height`, height);
Upvotes: 1
Reputation: 328
Use this to example, but JavaScript section is simplify to you.
function SVGPNG(svg, cb) {
let temp = document.createElement("img");
let imageSrc = URL.createObjectURL(
new Blob([svg], { type: "image/svg+xml" })
);
temp.src = imageSrc;
temp.setAttribute("style", "position:fixed;left:-200vw;");
document.body.appendChild(temp);
temp.onload = function onload() {
let canvas = document.createElement("canvas");
canvas.width = temp.clientWidth;
canvas.height = temp.clientHeight;
let ctx = canvas.getContext("2d");
ctx.drawImage(temp, 0, 0);
let src = canvas.toDataURL("image/png");
cb(src, canvas);
temp.remove();
URL.revokeObjectURL(imageSrc);
};
}
function onPaste(e) {
SVGPNG(e.target.value, (src) => {
document.getElementById("output").value = src;
});
}
body {
font-family: system-ui;
background: #f06d06;
color: white;
text-align: center;
}
textarea {
border: solid 1px #ccc;
border-radius: 10px;
resize: none;
outlined: solid 1px #999;
}
<textarea cols="60" rows="20" autofocus onchange="onPaste">Paste your SVG code here...</textarea>
<textarea cols="60" rows="20" readonly id="output">Your output here...</textarea>
Upvotes: 0
Reputation: 482
The answer by @ciro costa does help, but the generated png height is not working correctly unless the canvas height and width is set.
function downloadImg() {
const svgElem = document.querySelector('svg')
const serializer = new XMLSerializer();
let svgData = serializer.serializeToString(svgElem);
svgData = '<?xml version="1.0" standalone="no"?>\r\n' + svgData;
const svgBlob = new Blob([svgData], {
type: 'image/svg+xml;charset=utf-8',
});
let DOMURL = window.URL || window.webkitURL || window;
const url = DOMURL.createObjectURL(svgBlob);
const img = new Image();
img.onload = () => {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const domRect = svgElem.getBBox();
canvas.width = domRect.width;
canvas.height = domRect.height;
ctx.drawImage(img, 0, 0, domRect.width, domRect.height);
DOMURL.revokeObjectURL(url);
const imgURI = canvas
.toDataURL('image/png')
.replace('image/png', 'image/octet-stream');
download(imgURI);
};
img.onerror = (e) => {
console.error('Image not loaded', e);
};
img.src = url;
}
function download(href) {
let download = document.createElement('a');
download.href = href;
download.download = 'img.png';
download.click();
download.remove();
}
<svg width="256" height="256" id="icon">
<rect class="bg1" id="bg_color_1" x="0" y="0" width="256" height="256" fill="#4cbc5a" />
</svg>
<div>
<button onclick="downloadImg()">Download</button>
</div>
Upvotes: 3
Reputation: 99
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
Upvotes: 5
Reputation: 1
Keeping it simple, place an svg, a canvas and an empty img into the HTML. Set all to the same size. The javascript will use the svg to create a binary large object which is then rendered in the canvas as a png image. The function call creates a clone of the canvas and converts it into a jpeg.
function fjpg(){
clone = c.cloneNode(true);
ctx = clone.getContext('2d');
ctx.fillStyle = "#FFF";
ctx.fillRect(0, 0, clone.width, clone.height);
ctx.drawImage(c, 0, 0);
document.all.jp1.src=clone.toDataURL("image/jpeg");
ctx = c.getContext('2d');
svgBlob = new Blob( [dataPNG], { type: 'image/svg+xml' } );
urlPNG = self.URL.createObjectURL( svgBlob );
img = new Image();
img.onload = function () {ctx.drawImage(img,0,0)}
img.src = urlPNG;
}
c = document.all.canvas0;
ctx = c.getContext('2d');
data = (new XMLSerializer()).serializeToString(document.all.svg0);
dataJPG = data.replace('>SVG<','>JPG<');
dataPNG = data.replace('>SVG<','>PNG<');
svgBlob = new Blob( [dataJPG], { type: 'image/svg+xml' } );
urlJPG = self.URL.createObjectURL( svgBlob );
img = new Image();
img.onload = function () {
ctx.drawImage( img, 0, 0 );
fjpg();
}
img.src = urlJPG;
<svg id='svg0' height=180 width=180><rect width=100% height=100% fill=red /><circle cx=90 cy=90 r=80 fill=green /><text x=90 y=105 font-size=60 text-anchor=middle fill=yellow>SVG</text></svg>
<canvas id="canvas0" height=180 width=180></canvas>
<img src='' id='jp1'>
Upvotes: -1
Reputation: 153
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);
});
Upvotes: 1
Reputation: 6058
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);
}
}
Upvotes: 5