SVG to Image returning blank image blob - javascript

I have an interactive drawing app on my website, and I want to create a button where one could share their drawing on FB.
I'm trying to convert the SVG element to a blob, to then pass it to og:image, but I'm having some issues with the conversion.
I have two trials:
one doesn't trigger the onload function for some reason.
The other returns an empty blob
both trials work fine on jsfiddle however.
First Attempt
var xmlSerializer = new XMLSerializer();
var svgString = xmlSerializer.serializeToString(document.querySelector("#svg"));
var canvas = document.createElement("canvas");
var bounds = {
width: 1040,
height: 487
};
canvas.width = bounds.width;
canvas.height = bounds.height;
var ctx = canvas.getContext("2d");
var DOMURL = self.URL || self.webkitURL || self;
var img = new Image();
var svg = new Blob([svgString], {
type: "image/svg+xml;charset=utf-8"
});
var url = DOMURL.createObjectURL(svg);
img.onload = function() {
ctx.drawImage(img, 0, 0);
var png = canvas.toDataURL("image/png");
var mg = document.createElement("img");
mg.setAttribute("src", png);
document.body.appendChild(mg);
DOMURL.revokeObjectURL(png);
};
img.id = "testimg";
img.setAttribute("src", url);
Second Attempt
var svgString = new XMLSerializer().serializeToString(document.querySelector("svg"));
var canvas = document.createElement('CANVAS');
var ctx = canvas.getContext("2d");
var DOMURL = self.URL || sel.webkitURL || self;
var img = new Image();
var svg = new Blob([svgString], {
type: "image/svg+xml;charset=utf-8"
});
var url = DOMURL.createObjectURL(svg);
img.onload = function() {
ctx.drawImage(img, 0, 0);
var png = canvas.toDataURL("image/png");
var container = document.createElement('DIV');
container.innerHTML = '<img src="' + png + '"/>';
DOMURL.revokeObjectURL(png);
};
img.src = url;
document.body.appendChild(img);
Here's the app with the two attempts triggered by the two buttons "test1" and "test2"

The problem lies in the way you did define the xmlns:xlink attributes.
Currently from your page doing document.querySelector("use").attributes.getNamedItem("xmlns:xlink").namespaceURI will return null. This means that this attribute has been defined in the Document's namespace (HTML), so when you'll stringify it using the XMLSerializer, you will actually have two xmlns:xlink attributes on your elements, one in the HTML namespace, and the SVG one that is implied in an SVG embed in an HTML document.
It is invalid to have two same attributes on the same element in SVG, and thus your file is invalid and the image won't load.
If you are facing this issue it's certainly because you did set this attribute through JavaScript:
const newUse = document.createElementNS("http://www.w3.org/2000/svg", "use");
newUse.setAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink");
newUse.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", "#foo");
document.querySelector("svg").append(newUse);
console.log("set from markup:", document.querySelector("use").attributes.getNamedItem("xmlns:xlink").namespaceURI);
console.log("(badly) set from JS:", document.querySelector("use+use").attributes.getNamedItem("xmlns:xlink").namespaceURI);
// the last <use> has two xmlns:xlink attributes
console.log("serialization:", new XMLSerializer().serializeToString(document.querySelector("svg")));
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30">
<use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#foo"/>
</svg>
To set it correctly, you need to use setAttributeNS() and use the XMLNS namespace:
const newUse = document.createElementNS("http://www.w3.org/2000/svg", "use");
document.querySelector("svg").append(newUse);
// beware the last "/"
newUse.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:xlink", "http://www.w3.org/1999/xlink");
console.log("set from markup", document.querySelector("use").attributes.getNamedItem("xmlns:xlink").namespaceURI);
console.log("(correctly) set from JS", document.querySelector("use+use").attributes.getNamedItem("xmlns:xlink").namespaceURI);
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30">
<use xmlns:xlink="http://www.w3.org/1999/xlink"/>
</svg>
However the best is to not set at all these attributes.
As I said above, SVGs embedded in HTML do automatically have the correct xmlns and xlink namespaces defined without the need for attributes. And since you are creating your elements through JS, you already do define them in the correct namespace too.
So don't bother with these attributes:
const SVGNS = "http://www.w3.org/2000/svg";
const svg = document.createElementNS(SVGNS, "svg");
// To be able to draw an SVG image on a canvas in Firefox
// you must set absolute width and height to the root svg node
svg.setAttribute("width", 50);
svg.setAttribute("height", 50);
const target = document.createElementNS(SVGNS, "symbol");
target.id = "target";
const rect = document.createElementNS(SVGNS, "rect");
rect.setAttribute("width", 50);
rect.setAttribute("height", 50);
rect.setAttribute("fill", "green");
const use = document.createElementNS(SVGNS, "use");
// since SVG2 we don't even need to set href in the xlink NS
use.setAttribute("href", "#target");
target.append(rect);
svg.append(target, use);
const svgString = new XMLSerializer().serializeToString(svg);
console.log(svgString); // contains all the NS attributes
const blob = new Blob([svgString], { type: "image/svg+xml" });
const img = new Image();
img.src = URL.createObjectURL(blob);
document.body.append(img);

Related

How to convert multiple SVG's to one PNG image?

I need to convert 2 separate SVG's into 1 PNG image. I currently have code working for converting 1 SVG to a PNG. This code is shown below, I convert the SVG to canvas to PNG.
The SVG's are on the same page, and I have tried using an array to iterate through them and grab both. The code I am currently working on is also below, but is not working. Any advice on how to convert the SVGs to the same PNG image?
Thanks a bunch!
Single SVG to PNG
// convert svg to png
function svgToPng(){
var svg = document.querySelectorAll('svg')[0];
// clone svg nodes
var copy = svg.cloneNode(true);
// convert to xml format for storage/transportation
var serializer = new XMLSerializer();
var data = serializer.serializeToString(copy);
// create url
var DOMURL = window.URL || window.webkitURL || window;
// create image/blob
var img = new Image();
var blob = new Blob([data], {type: "image/svg+xml;charset=utf-8"});
var url = DOMURL.createObjectURL(blob);
// create canvas
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
// draw image on canvas
img.onload = function(){
ctx.drawImage(img, 0, 0, $(svg0).width(), $(svg0).height());
DOMURL.revokeObjectURL(url, url1);
var imgURI = canvas.toDataURL("image/png").replace("image/png", "image/octet-stream");
}
// send new image to window
img.src = url;
// return img.src;
window.open().document.write('<img src="' + img.src + '" width="1600" height="800"/>');
};
Multiple SVG's to PNG
// convert multipl svgs to png
function multSvgToPng(){
var array = [];
var totalSvg = document.querySelectorAll('svg'); // NodeList(2) [svg, svg]
// create array
for(var i = 0; i < totalSvg.length; i++){
array = document.querySelectorAll('svg')[i];
}
for(var j = 0; j < array.length; j++){
var svg = array[j];
var copy = svg.cloneNode(true);
var serializer = new XMLSerializer();
var data = serializer.serializeToString(copy);
var DOMURL = window.URL || window.webkitURL || window;
var img = new Image();
var blob = new Blob([data], {type: "image/svg+xml;charset=utf-8"});
var url = DOMURL.createObjectURL(blob);
var svgStr = serializer.serializeToString(copy);
}
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
img.onload = function(){
ctx.drawImage(img, 0, 0, $(svg).width(), $(svg).height());
DOMURL.revokeObjectURL(url);
var imgURI = canvas.toDataURL("image/png").replace("image/png", "image/octet-stream");
}
// send new image to window
img.src = url;
// return img.src;
window.open().document.write('<img src="' + img.src + '" width="1600" height="800"/>');
}
Example SVG Images
<!DOCTYPE html>
<html>
<body>
<svg height="100" width="100">
<circle cx="50" cy="50" r="40" stroke="black" stroke-width="3" fill="red" />
Sorry, your browser does not support inline SVG.
</svg>
</body>
</html>
<!DOCTYPE html>
<html>
<body>
<svg width="400" height="110">
<rect width="300" height="100" style="fill:rgb(0,0,255);stroke-width:3;stroke:rgb(0,0,0)" />
Sorry, your browser does not support inline SVG.
</svg>
</body>
</html>
I've used your technique and made two separate functions. The first function drawSVGToCanvas creates a canvas for a single SVG, by converting it to a blob and drawing it onto the canvas when the image has loaded. It returns a promise with the canvas element when it is fullfilled.
convertSVGsToSingleImage accepts a list of SVG elements for which it will loop over and call drawSVGToCanvas for each of those SVG elements. It waits until they are rendered and continues to draw the returned canvas elements on a single new canvas. This is where the combining happens.
const preview = document.getElementById('preview');
const svgs = document.querySelectorAll('svg');
function drawSVGToCanvas(svg) {
const { width, height } = svg.getBoundingClientRect();
const serializer = new XMLSerializer();
const copy = svg.cloneNode(true);
const data = serializer.serializeToString(copy);
const image = new Image();
const blob = new Blob([data], {
type: 'image/svg+xml;charset=utf-8'
});
const url = URL.createObjectURL(blob);
return new Promise(resolve => {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = width;
canvas.height = height;
image.addEventListener('load', () => {
ctx.drawImage(image, 0, 0, width, height);
URL.revokeObjectURL(url);
resolve(canvas);
}, { once: true });
image.src = url;
})
}
async function convertSVGsToSingleImage(svgs, format = 'image/png') {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const drawSVGs = Array.from(svgs).map(svg => drawSVGToCanvas(svg))
const renders = await Promise.all(drawSVGs);
canvas.width = Math.max(...renders.map(render => render.width));
canvas.height = Math.max(...renders.map(render => render.height));
renders.forEach(render => ctx.drawImage(render, 0, 0, render.width, render.height));
const source = canvas.toDataURL(format).replace(format, 'image/octet-stream');
return source;
}
convertSVGsToSingleImage(svgs).then(source => {
const image = new Image();
image.addEventListener('load', () => {
preview.append(image);
})
image.src = source;
});
<svg width="400" height="110">
<rect width="300" height="100" style="fill:rgb(0,0,255);stroke-width:3;stroke:rgb(0,0,0)" />
Sorry, your browser does not support inline SVG.
</svg>
<svg height="100" width="100">
<circle cx="50" cy="50" r="40" stroke="black" stroke-width="3" fill="red" />
Sorry, your browser does not support inline SVG.
</svg>
<div id="preview"></div>

How to fill canvas and add text

I want to fill my canvas with a image (base64-string) and than add a text into the canvas.
Initial idea (javascript browser application): I want to set the base64-string txtb64 (Image) as background image of the canvas and than add the text over it.
downloadtext: function() {
var sign = this.getView().byId("SigId");
var txtb64 = signpad.getSignAsJpeg();
var canvas = document.createElement("canvas");
var ctx = canvas.getContext('2d');
var image = new Image();
image.src = txtb64;
image.addEventListener("load", function(event) {
console.log("Ready!");
});
ctx.drawImage(image, 0, 0);
ctx.fillText('My random text', 0, 0);
var dataURL = canvas.toDataURL("image/jpeg");
var image = new Image();
var element = document.createElement('a');
image.src = dataURL;
element.setAttribute('href', image.src);
element.setAttribute('download', 'image');
element.style.display = 'none';
element.click();
},
The problem is I always get a black rectangle as output.
What is wrong with my code, because i can see no error.
It looks like you should put the code needed to download the new image inside the "load" event handler of the image you create. This is needed because the load event could trigger after the code beneath it runs, resulting in an empty image being put onto the canvas.
Also you declare var image = new Image(); twice, which could also be causing issues. Calling one image1 and the other image2 will prevent that.
WARNING Running this snippet triggers a download request.
var data = "data:image/png;base64,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";
var can = document.getElementById('can');
var ctx = can.getContext('2d');
var img = new Image();
img.src = data;
img.addEventListener('load', e =>
{
ctx.drawImage(img, 0, 0);
ctx.fillText('My random text', 100, 100);
var dataURL = can.toDataURL("image/jpeg");
var image = new Image();
var element = document.createElement('a');
image.src = dataURL;
element.setAttribute('href', image.src);
element.setAttribute('download', 'image');
document.body.appendChild(element);
element.click();
});
<canvas width="300" height="300" id="can"></canvas>

Overlay 2x image blobs

I've created a function that converts a Mapbox GL JS canvas to a png image blob.
map.getCanvas().toBlob(function (blob) {
mapBlob=blob;
saveAs(blob, 'map.png');
})
Unfortunatley this does not include DOM elements, one of which is the scalebar. I can create a seperate blob of the scalebar using dom-to-image.
This looks like below and creates a perfectly sized image blob of the scalebar.
var node = document.getElementById('scaleBar');
domtoimage.toBlob(node)
.then(function (blob) {
scaleBlob=blob
saveAs(scaleBlob, 'scale.png');
})
After these two functions I have 2x image blobs that I'd like to combine (scale bar on top of map).
Using Javascript, how can I combine the now named variables scaleBlob and mapBlob to a single blob, which will then be saved as a png locally?
Examples of the saved map image
And saved scaled image (just a line really but sized appropriately)
And this is what I want as a new, final image
Here is an example:
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var circle = "<svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' xml:space='preserve' height='150' width='150'><circle cx='50' cy='50' r='20' stroke='black' stroke-width='3' fill='red' /></svg> ";
var blob1 = new Blob([circle], {type: "image/svg+xml;charset=utf-8"});
var rect = "<svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' xml:space='preserve' height='50' width='50'><rect width='30' height='50' style='fill:rgb(0,0,255)'/></svg> ";
var blob2 = new Blob([rect], {type: "image/svg+xml;charset=utf-8"});
renderImage(blob1, 0, 0)
renderImage(blob2, 20, 50)
function renderImage(blob, x , y){
var img = new Image();
img.onload = function(){ ctx.drawImage(img, x, y) }
img.src = URL.createObjectURL(blob);
};
<canvas id = 'canvas'></canvas>
Here I'm just putting the blobs from svg, but on the same way you could do with two different blobs, and at the end you can do the saveAs

Image cropped while converting SVG to PNG

While saving an SVG to PNG, the image saved contains only the SVG rendered in the viewbox/window. How can one save a large PNG, containing the whole SVG?
// SVG element and XML string.
var svg = document.querySelector('svg');
var svgData = new XMLSerializer().serializeToString(svg);
// Canvas to hold the image.
var canvas = document.createElement('canvas');
var context = canvas.getContext('2d');
// Canvas size = SVG size.
var svgSize = svg.getBoundingClientRect();
canvas.width = svgSize.width;
canvas.height = svgSize.height;
// Image element appended with data.
var img = document.createElement('img');
img.setAttribute('src', 'data:image/svg+xml;base64,' + btoa(svgData));
img.onload = function() {
// Draw image on canvas and convert to URL.
context.drawImage(img,0,0);
console.log(canvas.toDataURL('image/png'));
};
Instead of:
var svgSize = svg.getBoundingClientRect();
Use:
var svgSize = svg.viewBox.baseVal;
This will get you the true dimensions of the viewBox.
REFERENCE
https://stackoverflow.com/a/7682976/2813224
SNIPPET
// SVG element and XML string.
var svg = document.querySelector('svg');
var svgData = new XMLSerializer().serializeToString(svg);
// Canvas to hold the image.
var canvas = document.createElement('canvas');
var context = canvas.getContext('2d');
// Canvas size = SVG size.
var svgSize = svg.viewBox.baseVal;
canvas.width = svgSize.width;
canvas.height = svgSize.height;
// Image element appended with data.
var img = document.createElement('img');
img.setAttribute('src', 'data:image/svg+xml;base64,' + btoa(svgData));
img.onload = function() {
// Draw image on canvas and convert to URL.
context.drawImage(img,0,0);
console.log(canvas.toDataURL('image/png'));
};
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Layer_1" x="0px" y="0px" width="64px" height="64px" viewBox="-0.5 0.5 64 64" enable-background="new -0.5 0.5 64 64" xml:space="preserve">
<g>
<circle fill="#FFFFFF" cx="31.325" cy="32.873" r="30.096"/>
<path id="text2809_1_" d="M31.5,14.08c-10.565,0-13.222,9.969-13.222,18.42c0,8.452,2.656,18.42,13.222,18.42 c10.564,0,13.221-9.968,13.221-18.42C44.721,24.049,42.064,14.08,31.5,14.08z M31.5,21.026c0.429,0,0.82,0.066,1.188,0.157 c0.761,0.656,1.133,1.561,0.403,2.823l-7.036,12.93c-0.216-1.636-0.247-3.24-0.247-4.437C25.808,28.777,26.066,21.026,31.5,21.026z M36.766,26.987c0.373,1.984,0.426,4.056,0.426,5.513c0,3.723-0.258,11.475-5.69,11.475c-0.428,0-0.822-0.045-1.188-0.136 c-0.07-0.021-0.134-0.043-0.202-0.067c-0.112-0.032-0.23-0.068-0.336-0.11c-1.21-0.515-1.972-1.446-0.874-3.093L36.766,26.987z"/>
<path id="path2815_1_" d="M31.433,0.5c-8.877,0-16.359,3.09-22.454,9.3c-3.087,3.087-5.443,6.607-7.082,10.532 C0.297,24.219-0.5,28.271-0.5,32.5c0,4.268,0.797,8.32,2.397,12.168c1.6,3.85,3.921,7.312,6.969,10.396 c3.085,3.049,6.549,5.399,10.398,7.037c3.886,1.602,7.939,2.398,12.169,2.398c4.229,0,8.34-0.826,12.303-2.465 c3.962-1.639,7.496-3.994,10.621-7.081c3.011-2.933,5.289-6.297,6.812-10.106C62.73,41,63.5,36.883,63.5,32.5 c0-4.343-0.77-8.454-2.33-12.303c-1.562-3.885-3.848-7.32-6.857-10.33C48.025,3.619,40.385,0.5,31.433,0.5z M31.567,6.259 c7.238,0,13.412,2.566,18.554,7.709c2.477,2.477,4.375,5.31,5.67,8.471c1.296,3.162,1.949,6.518,1.949,10.061 c0,7.354-2.516,13.454-7.506,18.33c-2.592,2.516-5.502,4.447-8.74,5.781c-3.2,1.334-6.498,1.994-9.927,1.994 c-3.468,0-6.788-0.653-9.949-1.948c-3.163-1.334-6.001-3.238-8.516-5.716c-2.515-2.514-4.455-5.353-5.826-8.516 c-1.333-3.199-2.017-6.498-2.017-9.927c0-3.467,0.684-6.787,2.017-9.949c1.371-3.2,3.312-6.074,5.826-8.628 C18.092,8.818,24.252,6.259,31.567,6.259z"/>
</g>
</svg>
This is because you are setting your canvas size to your rendered svg ones.
In your CSS, you probably do resize your svg, which results in a difference between it's computed size and it's natural one.
By default, drawImage(img, dx, dy, dWidth, dHeight) will use the source's width and height as destinationWidth and destinationHeight if these parameters are not passed.
You can check this example showing the same behavior with a raster image :
window.onload = function(){
var canvas = document.createElement('canvas');
var context = canvas.getContext('2d');
var svgSize = inDoc.getBoundingClientRect();
canvas.width = svgSize.width;
canvas.height = svgSize.height;
var img = document.createElement('img');
img.setAttribute('src', inDoc.src);
img.onload = function() {
context.drawImage(img,0,0);
document.body.appendChild(canvas);
};
}
img{ width: 64px; height: 64px}
<img id="inDoc" src="http://lorempixel.com/128/128"/>
And here is a little graphic showing what's happening.
So the solution is just to set your canvas' width and height properties to your img's ones, just like you would do with any other source :
img.onload = function(){
canvas.width = this.width;
canvas.height = this.height;
ctx.drawImage(this, 0,0);
}
and if you want to include some scaling factor :
img.onload = function(){
canvas.width = this.width * scale;
canvas.height = this.height * scale;
ctx.drawImage(this, 0,0, canvas.width, canvas.height);
}
Now, one not related to your actual code but still huge difference between raster images and svg images is that svg width and height can be set to relative units (like %).
Browsers have no direct clue about what it's relative to. (Chrome does a guess, I don't know how, others won't render your image).
So you need to check for this before exporting to a dataURI :
var absoluteUnits = [1,5,6,7,8,9,10];
if (absoluteUnits.indexOf(svg.width.baseVal.unitType)<0) {
svg.setAttribute('width', aboluteWidth);
}
if (absoluteUnits.indexOf(svg.height.baseVal.unitType)<0) {
svg.setAttribute('height', aboluteHeight);
}
Here absoluteWidth and absoluteHeight can be the results of svg.getBoundingClientRect().
Also note that IE9 won't be able to show the img.width and img.height values, so you've got to make a special case for it...
... but since you should have already checked for the absoluteSize, this should not be a problem :
var svg = document.querySelector('svg');
// we'll use a copy to not modify the svg in the document
var copy = svg.cloneNode(true);
var absoluteWidth, absoluteHeight, BBox;
var absoluteUnits = [1,5,6,7,8,9,10];
if (absoluteUnits.indexOf(svg.width.baseVal.unitType)<0) {
BBox = svg.getBoundingClientRect();
absoluteWidth = BBox.width
copy.setAttribute('width', absoluteWidth);
}
else{
absoluteWidth = svg.getAttribute('width');
}
if (absoluteUnits.indexOf(svg.height.baseVal.unitType)<0) {
if(!BBox){
BBox = svg.getBoundingClientRect();
}
absoluteHeight = BBox.height;
copy.setAttribute('height', absoluteHeight)
}
else{
absoluteHeight = svg.getAttribute('height');
}
var svgData = new XMLSerializer().serializeToString(copy);
var canvas = document.createElement('canvas');
var context = canvas.getContext('2d');
var img = document.createElement('img');
img.setAttribute('src', 'data:image/svg+xml;base64,' + btoa(svgData));
img.onload = function() {
// here you set your canvas width and height
canvas.width = this.width || absoluteWidth;
canvas.height = this.height || absoluteHeight;
context.drawImage(img,0,0);
document.body.appendChild(canvas);
};
svg{width: 64px; height:64px; border: 1px solid green;}
canvas{border: 1px solid blue;}
<svg width="128px" height="128px" viewBox="0 0 128 128">
<rect x="20" y="20" width="84" height="84"/>
</svg>

Issue Textpath svg to png

I'm working on an SVG graphic export (with d3.js library) to PNG. The problem is the label textPath. When exporting to PNG, the text does not appear. Does anyone know if there is solution for this problem?
The code I'm using to do the conversion is:
var svgString = new XMLSerializer().serializeToString(document.querySelector('#svg'));
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var DOMURL = self.URL || self.webkitURL || self;
var img = new Image();
var svg = new Blob([svgString], {type: "image/svg+xml;charset=utf-8"});
var url = DOMURL.createObjectURL(svg);
img.onload = function() {
ctx.drawImage(img, 0, 0);
var png = canvas.toDataURL("image/png");
var a = document.createElement("a");
a.download = "grafico.png";
a.href = png;
a.click();
DOMURL.revokeObjectURL(png);
};
img.src = url;
Thank you very much to all.
A greeting,
Sonia
Fixed. I've solved with attributes in the css style-sheet such as:
font-size: 16px;
color: black;
fill: none;
Same issue here. It seems that this method of conversion is not getting the body's font-size. However, dumping the generated SVG into a .svg file and opening that file in the browser renders correctly.

Categories

Resources