I have data from a database that draws a graph using NVD3. I then want to work toward PDFing the report page with dompdf. It seems that the "basic SVG support" of dompdf does not work well with my bar chart. So I figure I will convert the SVG to a PNG fisrt.
This answer works well for displaying the SVG as a PNG without styling: https://stackoverflow.com/a/19269812
Code:
var el = $($('svg')[0]);
var svgMarkup = '<svg xmlns="http://www.w3.org/2000/svg"'
+ ' class="' + el.attr('class') +'"'
+ ' width="' + el.attr('width') +'"'
+ ' height="' + el.attr('height') +'"'
+ '>'
+ $('svg')[0].innerHTML.toString()+'</svg>';
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var DOMURL = this.URL || this.webkitURL || this;
var img = new Image();
var svg = new Blob([svgMarkup], {type: "image/svg+xml;charset=utf-8"});
var url = DOMURL.createObjectURL(svg);
img.onload = function() {
ctx.drawImage(img, 0, 0);
alert('ok');
DOMURL.revokeObjectURL(url);
};
img.src = url;
There is another answer on the same question that addresses the styling, but I can't get the image to open at the correct size on the same screen i.e. without opening a new tab (even then, the image is cropped). https://stackoverflow.com/a/38085847
Code:
var style = "\n";
var requiredSheets = ['phylogram_d3.css', 'open_sans.css']; // list of required CSS
for (var i=0; i<document.styleSheets.length; i++) {
var sheet = document.styleSheets[i];
if (sheet.href) {
var sheetName = sheet.href.split('/').pop();
if (requiredSheets.indexOf(sheetName) != -1) {
var rules = sheet.rules;
if (rules) {
for (var j=0; j<rules.length; j++) {
style += (rules[j].cssText + '\n');
}
}
}
}
}
var svg = d3.select("svg"),
img = new Image(),
serializer = new XMLSerializer(),
// prepend style to svg
svg.insert('defs',":first-child")
d3.select("svg defs")
.append('style')
.attr('type','text/css')
.html(style);
// generate IMG in new tab
var svgStr = serializer.serializeToString(svg.node());
img.src = 'data:image/svg+xml;base64,'+window.btoa(unescape(encodeURIComponent(svgStr)));
window.open().document.write('<img src="' + img.src + '"/>');
So, using Javascript, how can I convert an SVG to a PNG? I'm trying combinations of the two, but I think my problem is that the d3 selector used in the second snippet is too different from the SVG markup method used in the first:
var style = "\n";
var requiredSheets = ['default-blue-white.css']; // list of required CSS
for (var i=0; i<document.styleSheets.length; i++) {
var sheet = document.styleSheets[i];
if (sheet.href) {
var sheetName = sheet.href.split('/').pop();
if (requiredSheets.indexOf(sheetName) != -1) {
var rules = sheet.rules;
if (rules) {
for (var j=0; j<rules.length; j++) {
style += (rules[j].cssText + '\n');
}
}
}
}
}
var svgX = d3.select("svg");
// prepend style to svg
svgX.insert('defs',":first-child");
d3.select("svg defs")
.append('style')
.attr('type','text/css')
.html(style);
var el = $($('svg')[0]);
var svgMarkup = '<svg xmlns="http://www.w3.org/2000/svg"'
+ ' class="' + el.attr('class') +'"'
+ ' width="' + el.attr('width') +'"'
+ ' height="' + el.attr('height') +'"'
+ '>'
+ $('svg')[0].innerHTML.toString()+'</svg>';
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var DOMURL = this.URL || this.webkitURL || this;
var img = new Image();
var svg = new Blob([svgMarkup], {type: "image/svg+xml;charset=utf-8"});
var url = DOMURL.createObjectURL(svg);
img.onload = function() {
ctx.drawImage(img, 0, 0);
alert('ok');
DOMURL.revokeObjectURL(url);
};
img.src = url;
The production of a <svg> by cobbling up its attributes is much too naive, as you could see from the croppings and false sizes.
Going via a data URL is a viable way, but instead of opening that in a new window (where it would be a SVG image, not a PNG), write it to canvas.
Do not insert the stylesheets into the DOM with d3.js selection.html(), but use plain Javascript document.createCDATASection() and element.appendChild() to avoid ending up with invalid XML.
The following will replace the <svg> with a <canvas> in its place, and with the same size as it is rendered in the browser.
var styleRules = [];
var requiredSheets = [...]; // list of required CSS files
for (var sheet of document.styleSheet)) {
if (sheet.href) {
var sheetName = sheet.href.split('/').pop();
if (requiredSheets.indexOf(sheetName) != -1) {
var rules = Array.from(sheet.cssRules).map(rule => rule.cssText);
styleRules = styleRules.concat(rules);
}
}
}
var styleText = styleRules.join(' ');
var styleNode = document.createCDATASection(styleRules);
var svg = d3.select("svg"),
img = new Image(),
serializer = new XMLSerializer(),
// prepend style to svg
svg.insert('defs',":first-child")
var styleEl = d3.select("svg defs")
.append('style')
.attr('type','text/css');
styleEl.node().appendChild(styleNode);
var svgStr = serializer.serializeToString(svg.node());
img.src = 'data:image/svg+xml;base64,'+window.btoa(unescape(encodeURIComponent(svgStr)));
var bbox = svg.getBoundingClientRect();
var canvas = document.createElement("canvas");
canvas.width = bbox.width;
canvas.height = bbox.height;
canvas.getContext("2d").drawImage(img,0,0,bbox.width,bbox.width);
canvas.parentNode.replaceChild(canvas, svg);
Related
I am currently working on a Javascript project and I am struggling with exporting the entire SVG image on the canvas. So far I've been only able to export the visible part of the canvas, with out the "hidden" parts.
How do I capture the full canvas content?
Is there a way to do it without messing around with the original canvas size?
I am using D3.js V3
Screenshot of my project
Here's my code:
var svgString;
window.onload = function(){
setTimeout(function() {
exportSVG = document.getElementById("canvas");
document.getElementById("canvas").style.fontFamily= "lato";
document.getElementById("canvas").style.width= exportSVG.getBBox().width * 1;
document.getElementById("canvas").style.height= exportSVG.getBBox().height * 1;
svgString = getSVGString(exportSVG);
console.log(exportSVG.getBBox().width + " / " + exportSVG.getBBox().height);
svgString2Image(svgString, exportSVG.getBBox().width, exportSVG.getBBox().height, 'png', save); // passes Blob and filesize String to the callback
console.log("svg export code loaded");
// console.log(svgString.getBBox().width); document.getElementById("canvas").getBBox().width
}, 5000);
};
function save(dataBlob, filesize) {
saveAs(dataBlob, 'D3 vis exported to PNG.png'); // FileSaver.js function
}
// Below are the functions that handle actual exporting:
// getSVGString ( svgNode ) and svgString2Image( svgString, width, height, format, callback )
function getSVGString(svgNode) {
svgNode.setAttribute('xlink', 'http://www.w3.org/1999/xlink');
var cssStyleText = getCSSStyles(svgNode);
appendCSS(cssStyleText, svgNode);
var serializer = new XMLSerializer();
var svgString = serializer.serializeToString(svgNode);
svgString = svgString.replace(/(\w+)?:?xlink=/g, 'xmlns:xlink='); // Fix root xlink without namespace
svgString = svgString.replace(/NS\d+:href/g, 'xlink:href'); // Safari NS namespace fix
return svgString;
function getCSSStyles(parentElement) {
var selectorTextArr = [];
// Add Parent element Id and Classes to the list
selectorTextArr.push('#' + parentElement.id);
for (var c = 0; c < parentElement.classList.length; c++)
if (!contains('.' + parentElement.classList[c], selectorTextArr))
selectorTextArr.push('.' + parentElement.classList[c]);
// Add Children element Ids and Classes to the list
var nodes = parentElement.getElementsByTagName("*");
for (var i = 0; i < nodes.length; i++) {
var id = nodes[i].id;
if (!contains('#' + id, selectorTextArr))
selectorTextArr.push('#' + id);
var classes = nodes[i].classList;
for (var c = 0; c < classes.length; c++)
if (!contains('.' + classes[c], selectorTextArr))
selectorTextArr.push('.' + classes[c]);
}
// Extract CSS Rules
var extractedCSSText = "";
for (var i = 0; i < document.styleSheets.length; i++) {
var s = document.styleSheets[i];
try {
if (!s.cssRules) continue;
} catch (e) {
if (e.name !== 'SecurityError') throw e; // for Firefox
continue;
}
var cssRules = s.cssRules;
for (var r = 0; r < cssRules.length; r++) {
if (contains(cssRules[r].selectorText, selectorTextArr))
extractedCSSText += cssRules[r].cssText;
}
}
return extractedCSSText;
function contains(str, arr) {
return arr.indexOf(str) === -1 ? false : true;
}
}
function appendCSS(cssText, element) {
var styleElement = document.createElement("style");
styleElement.setAttribute("type", "text/css");
styleElement.innerHTML = cssText;
var refNode = element.hasChildNodes() ? element.children[0] : null;
element.insertBefore(styleElement, refNode);
}
}
function svgString2Image(svgString, width, height, format, callback) {
var format = format ? format : 'png';
var imgsrc = 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(svgString))); // Convert SVG string to data URL
var canvas = document.createElement("canvas");
var context = canvas.getContext("2d");
canvas.width = width;
canvas.height = height;
var image = new Image();
image.onload = function() {
context.clearRect(0, 0, width, height);
context.drawImage(image, 0, 0, width, height);
canvas.toBlob(function(blob) {
var filesize = Math.round(blob.length / 1024) + ' KB';
if (callback) callback(blob, filesize);
});
};
image.src = imgsrc;
}
Simply change your <svg> viewBox attribute before you serialize it to a string so that it displays everything:
var svg = document.querySelector('svg');
var toExport = svg.cloneNode(true); // avoids having to reset everything afterward
// grab its inner content BoundingBox
var bb = svg.getBBox();
// update its viewBox so it displays all its inner content
toExport.setAttribute('viewBox', bb.x + ' ' + bb.y + ' ' + bb.width + ' ' + bb.height);
toExport.setAttribute('width', bb.width);
toExport.setAttribute('height', bb.height);
var svgAsStr = new XMLSerializer().serializeToString(toExport);
var blob = new Blob([svgAsStr], {type: 'image/svg+xml'});
var img = new Image();
img.onload = drawToCanvas;
img.src = URL.createObjectURL(blob);
function drawToCanvas(evt) {
var canvas = document.createElement('canvas');
canvas.width = this.width;
canvas.height = this.height;
canvas.getContext('2d').drawImage(this, 0,0);
document.body.appendChild(canvas);
}
svg{border: 1px solid blue}
canvas{border: 1px solid green}
<svg width="50" height="50" viewBox="0 0 50 50">
<rect x="0" y="0" width="200" height="50" fill="#CCC"/>
</svg>
I am trying to export my chart data consisting of image and data in a PDF. It works fine for me in laptop browser, but while I am trying to do the same in Ipad, the img.onload() function is not firing. I am not sure but I feel like it is due to the size of the image that is rendering in it. Can anybody help me with it? Can we change the pixels of the image that is being rendered?
The code is given below:-
function exportData(exportType) {
if (exportType) {
if (exportType == "pdf") {
alert("clicked pdf")
console.log("pdf export ....")
vm.toggleCanvas = !vm.toggleCanvas;
var canvas = "";
var ctxt = "";
// var img = "";
var sheets = "";
var svgURL = "";
canvas = document.getElementById('canvas');
$log.log($window.screen.width)
//resizing image for PDF
if ($window.screen.width >= 1500) {
canvas.width = $window.screen.width - 150;
} else {
canvas.width = $window.screen.width;
}
console.log("before canvas")
ctxt = canvas.getContext('2d');
sheets = document.styleSheets;
svgURL = generateSVGWithStyling('svg');
// var img = document.createElement('img');
var img = new Image();
console.log("after image")
console.log(img)
alert("imb")
img.src = 'data:image/svg+xml; charset=utf8, ' + encodeURIComponent(svgURL);
img.onload = function () {
debugger
console.log("in load")
alert("in load")
ctxt.drawImage(this, 0, 0);
vm.cnvasImg = "";
vm.cnvasImg = canvas.toDataURL();
var content = "";
console.log("on load")
subExportData(vm.cnvasImg, exportType);
}
console.log(img.src)
} else {
vm.cnvasImg = "nothing";
subExportData(vm.cnvasImg, exportType);
}
}
}
Any help would be apprciated.
Can you please try with passing the width, height properties to Image constructor Object.
var img = new Image(Width, Height);
I have svg images in my page:
And I use to convert to png images (and download them):
https://github.com/CogComp/apelles/blob/master/public/index.html#L370-L390
var mySVG = $("#" + id + " svg")[0];
var imgId = id + "-img";
$(document.body).append('<img height="' + mySVG.getBBox().height + '" width="' + mySVG.getBBox().width + '" id="' + imgId + '" style="visibility: hidden; "/>');
var tgtImage = document.getElementById(imgId); // Where to draw the result
var can = document.createElement('canvas'); // Not shown on page
var ctx = can.getContext('2d');
var loader = new Image;
loader.width = can.width = tgtImage.width;
loader.height = can.height = tgtImage.height;
loader.onload = function () {
ctx.drawImage(loader, 0, 0, loader.width, loader.height);
tgtImage.src = can.toDataURL();
};
var svgAsXML = (new XMLSerializer).serializeToString(mySVG);
loader.src = 'data:image/svg+xml,' + encodeURIComponent(svgAsXML);
sleep(500).then(() => {
var url = tgtImage.src.replace(/^data:image\/[^;]/, 'data:application/octet-stream');
window.open(url);
});
Here is the output. Why is the output turned dark?
I tried changing the background color but didn't help.
Here is the link to the page btw: http://nlp.cogcomp.org/
Update: Checking the SVG code I think I have a background:
Despite having a background, I tried adding a background to the picture, but doesn't seem to help. Here are a couple of variations I have tried:
var svgQ = $("#" + id + " svg");
var mySVG = svgQ[0];
var rect = document.createElementNS(svgQ,'rect');
rect.setAttribute('x',10);
rect.setAttribute('y',10);
rect.setAttribute('width',50);
rect.setAttribute('height',50);
rect.setAttribute('fill','#EFEFEF');
mySVG.appendChild(rect);
// or mySVG.append(rect);
$("svg").each(function(){
$(this).css({ fill: '#000' });
// or $(this).attr("fill", '#000');
// or $(this).attr("fill", '#000000');
});
svgQ.css({ fill: '#000' });
// or svgQ.attr("fill", '#000');
// or svgQ.attr("fill", '#000000');
I am Alex and this is my first stack overflow question.
I have the below script.
-This script gets images from an api.
-For each image received from the api, an element is created
-for each image, a class attribute is created
-for each image, an id attribute is created.
-for each image, a element is created. For each of the , I dynamically append an onclick event. each onclick is associated with its own function.
the anchor1,2,3,4 and 5 function are made so each time a user clicks a selected image, that image is then place into a canvas. When that is done, the color thief js kicks in and displays the color palette of the image selected.
All of that works perfectly.
What I would like to do is instead of having a function for each image, is to have 1 function that would place the image clicked into the canvas.
Basically i think my code can be made less redundant.
// search the collection using a JSON call
function search(query) {
return $.getJSON("https://www.rijksmuseum.nl/api/en/collection?q=Q&key=r4nzV2tL&imgonly=True&ps=5&format=json".replace("Q", query));
}
var searchBtn = document.getElementById("search");
searchBtn.addEventListener("click", doSearch);
var resultD = document.getElementById("result");
var searchField = document.getElementById("query");
//search function starts here
function doSearch() {
$("#result").show(); // result div to show when making new search
resultD.innerHTML = "";
var searchString = searchField.value;
if (searchString !== "") {
search(searchString).done(function(data) {
for (var artObj in data.artObjects) {
var rImg = document.createElement("img"); // create the image
rImg.setAttribute("crossOrigin", "Anonymous"); //needed so I can actually copy the image for later use
rImg.setAttribute("class", "imageClass"); //needed so I can actually copy the image for later use
var link = document.createElement("a"); // create the link
link.setAttribute('href', '#'); // set link path
// link.href = "www.example.com"; //can be done this way too
rImg.src = data.artObjects[artObj].webImage.url; // the source of the image element is the url from rijks api
link.appendChild(rImg); // append image to link
resultD.appendChild(link); // append link with image to div
resultD.innerHTML += data.artObjects[artObj].title; // this is the title from rijks api
$("#result img").each(function (i, image){ //for each image create a different id
image.id = "image" + (i + 1);
});
$("#result a").each(function (i, anchor){ //for each anchor create a different id
anchor.id = "anchor" + (i + 1);
anchor.setAttribute('onclick', "anchor" + (i + 1)+'();return false;'); // set link path
//return false needed so to avoid page jump
});
resultD.innerHTML += "<br> <br> <br> <br>";
}
});
}
}//search function ends here
//for each image create size matching canvas
function anchor1(){
var c=document.getElementById("drawing1");
var ctx=c.getContext("2d");
var img=document.getElementById("image1");
c.height = img.height ;
c.width = img.width ;
ctx.drawImage(img,0,0,c.width, c.height);
$("#result").hide();
setTimeout(function() { //timeout for image load to canvas - start
var colorThief = new ColorThief();
var color = colorThief.getPalette(img, 18);
var newHTML = $.map(color, function(value) {
return('<div style="background-color:rgb(' + value.join(', ') + ')"> </div>'+'<br>');
});
$("#colors").html(newHTML.join(''));
}, 500); //timeout for image load to canvas - ends
}
function anchor2(){
var c=document.getElementById("drawing1");
var ctx=c.getContext("2d");
var img=document.getElementById("image2");
c.height = img.height ;
c.width = img.width ;
ctx.drawImage(img,0,0,c.width, c.height);
$("#result").hide();
setTimeout(function() { //timeout for image load to canvas - start
var colorThief = new ColorThief();
var color = colorThief.getPalette(img, 18);
var newHTML = $.map(color, function(value) {
return('<div style="background-color:rgb(' + value.join(', ') + ')"> </div>'+'<br>');
});
$("#colors").html(newHTML.join(''));
}, 500); //timeout for image load to canvas - ends
}
function anchor3(){
var c=document.getElementById("drawing1");
var ctx=c.getContext("2d");
var img=document.getElementById("image3");
c.height = img.height ;
c.width = img.width ;
ctx.drawImage(img,0,0,c.width, c.height);
$("#result").hide();
setTimeout(function() { //timeout for image load to canvas - start
var colorThief = new ColorThief();
var color = colorThief.getPalette(img, 18);
var newHTML = $.map(color, function(value) {
return('<div style="background-color:rgb(' + value.join(', ') + ')"> </div>'+'<br>');
});
$("#colors").html(newHTML.join(''));
}, 500); //timeout for image load to canvas - ends
}
function anchor4(){
var c=document.getElementById("drawing1");
var ctx=c.getContext("2d");
var img=document.getElementById("image4");
c.height = img.height ;
c.width = img.width ;
ctx.drawImage(img,0,0,c.width, c.height);
$("#result").hide();
setTimeout(function() { //timeout for image load to canvas - start
var colorThief = new ColorThief();
var color = colorThief.getPalette(img, 18);
var newHTML = $.map(color, function(value) {
return('<div style="background-color:rgb(' + value.join(', ') + ')"> </div>'+'<br>');
});
$("#colors").html(newHTML.join(''));
}, 500); //timeout for image load to canvas - ends
}
function anchor5(){
var c=document.getElementById("drawing1");
var ctx=c.getContext("2d");
var img=document.getElementById("image5");
c.height = img.height ;
c.width = img.width ;
ctx.drawImage(img,0,0,c.width, c.height);
$("#result").hide();
setTimeout(function() { //timeout for image load to canvas - start
var colorThief = new ColorThief();
var color = colorThief.getPalette(img, 18);
var newHTML = $.map(color, function(value) {
return('<div style="background-color:rgb(' + value.join(', ') + ')"> </div>'+'<br>');
});
$("#colors").html(newHTML.join(''));
}, 500); //timeout for image load to canvas - ends
}
You want to utilize the .on event with jquery which allows you to deal with dynamic data which is basically what you are dealing with.
You are generating different <a> links with an individual id to call your different functions like (anchor1, anchor2...) with $("#result a").each(function (i, anchor){ //for each anchor create a different id anchor.id = "anchor" + (i + 1);
Instead of generating a different id for each <a> just give them the same value, but make it a class and not a id. Id's should always be unique and never repeat, classes can be repeated. For instance <a class="anchor"><...</a>
Then when you want to deal with the data from that particular record/image you would call it as such.
$(document).on('click','.anchor', function () {
});
So here is a working example you can start with.
<a class="anchor" data-id="1">...</a>
$(document).on('click','.anchor', function () {
var anchor_id = $(this).attr('data-id').val();
var c=document.getElementById("drawing1");
var ctx=c.getContext("2d");
var img=document.getElementById("image"+anchor_id); //this will attach the id for that image here
c.height = img.height ;
c.width = img.width ;
ctx.drawImage(img,0,0,c.width, c.height);
$("#result").hide();
setTimeout(function() { //timeout for image load to canvas - start
var colorThief = new ColorThief();
var color = colorThief.getPalette(img, 18);
var newHTML = $.map(color, function(value) {
return('<div style="background-color:rgb(' + value.join(', ') + ')"> </div>'+'<br>');
});
$("#colors").html(newHTML.join(''));
}, 500); //timeout for image load to canvas - ends
});
http://api.jquery.com/on/
https://learn.jquery.com/events/handling-events/
http://html-tuts.com/jquery-this-selector/
I have an external SVG file which contains some embedded image tags in pattern. Whenever I convert this SVG into PNG using toDataURL(), the generated PNG images does not contain the image I have applied as pattern to some SVG paths. Is there any way to solve this problem?
Yes there are : append the svg into your document and encode all the included images to dataURIs.
I am writing a script that does this and also some other stuff like including external style-sheets and some other fix of where toDataURL will fail (e.g external elements referenced through xlink:href attribute or <funciri>).
Here is the function I wrote for parsing the images content :
function parseImages(){
var xlinkNS = "http://www.w3.org/1999/xlink";
var total, encoded;
// convert an external bitmap image to a dataURL
var toDataURL = function (image) {
var img = new Image();
// CORS workaround, this won't work in IE<11
// If you are sure you don't need it, remove the next line and the double onerror handler
// First try with crossorigin set, it should fire an error if not needed
img.crossOrigin = 'Anonymous';
img.onload = function () {
// we should now be able to draw it without tainting the canvas
var canvas = document.createElement('canvas');
canvas.width = this.width;
canvas.height = this.height;
// draw the loaded image
canvas.getContext('2d').drawImage(this, 0, 0);
// set our <image>'s href attribute to the dataURL of our canvas
image.setAttributeNS(xlinkNS, 'href', canvas.toDataURL());
// that was the last one
if (++encoded === total) exportDoc();
};
// No CORS set in the response
img.onerror = function () {
// save the src
var oldSrc = this.src;
// there is an other problem
this.onerror = function () {
console.warn('failed to load an image at : ', this.src);
if (--total === encoded && encoded > 0) exportDoc();
};
// remove the crossorigin attribute
this.removeAttribute('crossorigin');
// retry
this.src = '';
this.src = oldSrc;
};
// load our external image into our img
img.src = image.getAttributeNS(xlinkNS, 'href');
};
// get an external svg doc to data String
var parseFromUrl = function(url, element){
var xhr = new XMLHttpRequest();
xhr.onload = function(){
if(this.status === 200){
var response = this.responseText || this.response;
var dataUrl = 'data:image/svg+xml; charset=utf8, ' + encodeURIComponent(response);
element.setAttributeNS(xlinkNS, 'href', dataUrl);
if(++encoded === total) exportDoc();
}
// request failed with xhr, try as an <img>
else{
toDataURL(element);
}
};
xhr.onerror = function(){toDataURL(element);};
xhr.open('GET', url);
xhr.send();
};
var images = svg.querySelectorAll('image');
total = images.length;
encoded = 0;
// loop through all our <images> elements
for (var i = 0; i < images.length; i++) {
var href = images[i].getAttributeNS(xlinkNS, 'href');
// check if the image is external
if (href.indexOf('data:image') < 0){
// if it points to another svg element
if(href.indexOf('.svg') > 0){
parseFromUrl(href, images[i]);
}
else // a pixel image
toDataURL(images[i]);
}
// else increment our counter
else if (++encoded === total) exportDoc();
}
// if there were no <image> element
if (total === 0) exportDoc();
}
Here the svgDoc is called svg,
and the exportDoc() function could just be written as :
var exportDoc = function() {
// check if our svgNode has width and height properties set to absolute values
// otherwise, canvas won't be able to draw it
var bbox = svg.getBoundingClientRect();
if (svg.width.baseVal.unitType !== 1) svg.setAttribute('width', bbox.width);
if (svg.height.baseVal.unitType !== 1) svg.setAttribute('height', bbox.height);
// serialize our node
var svgData = (new XMLSerializer()).serializeToString(svg);
// remember to encode special chars
var svgURL = 'data:image/svg+xml; charset=utf8, ' + encodeURIComponent(svgData);
var svgImg = new Image();
svgImg.onload = function () {
var canvas = document.createElement('canvas');
// IE11 doesn't set a width on svg images...
canvas.width = this.width || bbox.width;
canvas.height = this.height || bbox.height;
canvas.getContext('2d').drawImage(svgImg, 0, 0, canvas.width, canvas.height);
doSomethingWith(canvas)
};
svgImg.src = svgURL;
};
But once again, you will have to append your svg into the document first (either through xhr or into an <iframe> or an <object> element, and you will have to be sure all your resources are CORS compliant (or from same domain) in order to get these rendered.
var svg = document.querySelector('svg');
var doSomethingWith = function(canvas) {
document.body.appendChild(canvas)
};
function parseImages() {
var xlinkNS = "http://www.w3.org/1999/xlink";
var total, encoded;
// convert an external bitmap image to a dataURL
var toDataURL = function(image) {
var img = new Image();
// CORS workaround, this won't work in IE<11
// If you are sure you don't need it, remove the next line and the double onerror handler
// First try with crossorigin set, it should fire an error if not needed
img.crossOrigin = 'anonymous';
img.onload = function() {
// we should now be able to draw it without tainting the canvas
var canvas = document.createElement('canvas');
canvas.width = this.width;
canvas.height = this.height;
// draw the loaded image
canvas.getContext('2d').drawImage(this, 0, 0);
// set our <image>'s href attribute to the dataURL of our canvas
image.setAttributeNS(xlinkNS, 'href', canvas.toDataURL());
// that was the last one
if (++encoded === total) exportDoc();
};
// No CORS set in the response
img.onerror = function() {
// save the src
var oldSrc = this.src;
// there is an other problem
this.onerror = function() {
console.warn('failed to load an image at : ', this.src);
if (--total === encoded && encoded > 0) exportDoc();
};
// remove the crossorigin attribute
this.removeAttribute('crossorigin');
// retry
this.src = '';
this.src = oldSrc;
};
// load our external image into our img
var href = image.getAttributeNS(xlinkNS, 'href');
// really weird bug that appeared since this answer was first posted
// we need to force a no-cached request for the crossOrigin be applied
img.src = href + (href.indexOf('?') > -1 ? + '&1': '?1');
};
// get an external svg doc to data String
var parseFromUrl = function(url, element) {
var xhr = new XMLHttpRequest();
xhr.onload = function() {
if (this.status === 200) {
var response = this.responseText || this.response;
var dataUrl = 'data:image/svg+xml; charset=utf8, ' + encodeURIComponent(response);
element.setAttributeNS(xlinkNS, 'href', dataUrl);
if (++encoded === total) exportDoc();
}
// request failed with xhr, try as an <img>
else {
toDataURL(element);
}
};
xhr.onerror = function() {
toDataURL(element);
};
xhr.open('GET', url);
xhr.send();
};
var images = svg.querySelectorAll('image');
total = images.length;
encoded = 0;
// loop through all our <images> elements
for (var i = 0; i < images.length; i++) {
var href = images[i].getAttributeNS(xlinkNS, 'href');
// check if the image is external
if (href.indexOf('data:image') < 0) {
// if it points to another svg element
if (href.indexOf('.svg') > 0) {
parseFromUrl(href, images[i]);
} else // a pixel image
toDataURL(images[i]);
}
// else increment our counter
else if (++encoded === total) exportDoc();
}
// if there were no <image> element
if (total === 0) exportDoc();
}
var exportDoc = function() {
// check if our svgNode has width and height properties set to absolute values
// otherwise, canvas won't be able to draw it
var bbox = svg.getBoundingClientRect();
if (svg.width.baseVal.unitType !== 1) svg.setAttribute('width', bbox.width);
if (svg.height.baseVal.unitType !== 1) svg.setAttribute('height', bbox.height);
// serialize our node
var svgData = (new XMLSerializer()).serializeToString(svg);
// remember to encode special chars
var svgURL = 'data:image/svg+xml; charset=utf8, ' + encodeURIComponent(svgData);
var svgImg = new Image();
svgImg.onload = function() {
var canvas = document.createElement('canvas');
// IE11 doesn't set a width on svg images...
canvas.width = this.width || bbox.width;
canvas.height = this.height || bbox.height;
canvas.getContext('2d').drawImage(svgImg, 0, 0, canvas.width, canvas.height);
doSomethingWith(canvas)
};
svgImg.src = svgURL;
};
window.onload = parseImages;
canvas {
border: 1px solid green !important;
}
<svg width="200" height="200" xmlns="http://www.w3.org/2000/svg" version="1.1">
<defs>
<pattern id="Pattern" x="0" y="0" width=".25" height=".25">
<image xlink:href="https://dl.dropboxusercontent.com/s/1alt1303g9zpemd/UFBxY.png" width="100" height="100"/>
</pattern>
</defs>
<rect fill="url(#Pattern)" x="0" y="0" width="200" height="200"/>
</svg>