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>
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,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAYAAAB5fY51AAAFpUlEQVR4nO3a0W7bOBRAQf//T3efFnAFibzeNN2ecgYIEiu2EoDCAUnp9QMg4vV//wMAU4IFZAgWkCFYQIZgARmCBWQIFpAhWECGYAEZggVkCBaQIVhAhmABGYIFZAgWkCFYQIZgARmCBWQIFpAhWECGYAEZggVkCBaQIVhAhmABGYIFZAgWkCFYQIZgARmCBWQIFpAhWECGYAEZggVkCBaQIVhAhmABGYIFZAgWkCFYQIZgARmCBWQIFpAhWECGYAEZggVkCBaQIVhAhmABGYIFZAgWkCFYQIZgARmCBWQIFpAhWECGYAEZggVkCBaQIVhAhmABGYIFZAgWkCFYQIZgARmCBWQIFpAhWECGYAEZggVkCBaQIVhAhmABGYIFZAgWkCFYQIZgARmCBWQIFpAhWECGYAEZggVkCBaQIVhAhmABGYIFZAgWkCFYQIZgARmCBWQIFpAhWECGYAEZggVkCBaQIVhAhmABGYIFZAgWkCFYQIZgARmCBWQIFpAhWECGYAEZggVkCBaQIVhAhmABGYIFZAgWkCFYQIZgARmCBWQIFpAhWECGYAEZggVkCBaQIVhAhmABGYIFZAgWkCFYQIZgARmCBWQIFpAhWECGYAEZggVkCBaQIVhAhmCR8Hq9fvriTEaeP95doETrTEadP9IqSGZa5zLi/HZPsXk/voqSYJ3LiPNb7Pagrsev8bp7P+cx6ny7a1w+idbTuQTrTEadb/UUmKcZ113MnmZmonUeI863WoXp+p7Jea7HOIsR51vcBWkXneuG+905d0tG/m5GnC/bLeOeQrP6Pp2ZcRYjzpdMlnq7u4PT89x9hrMYcf6zp2emJjOup/Ncj01/5gxGnC/b3embzpCeHhxdhZGzGHE+9hSK6ab49NGFuyWlO4VnM9p8ZBWh68+7GdPTOe9mUatZGecw2ox9ZV9pGqDVeTzWgBFnbDqLuvv9J3cL349Nlo6cw6gz9rS/dH29CttuaTg9H2cy+oztQnR9vXuEYXo3cfV/cBYjz0eeHi1Y3d375HzX49dj17/FWYw8H1nFYrekm0btaSa3+/v8/Yw+Y7ul31Oc7l5fz3f3PpHiypXA2Go5ONksfwrbKnh3f4NzuQL4yG6mtFoCrs65u8O4OwdncAXwkcnjCO/f7z6zmjV9smHPeVwRjO32m6ZLw9W5V/tg4IpgZDfr2e1nrT6z+jy8c0WwNd23muxf7WZc030vzuSKYGn62MLq83evd48/2MfijquBpadwTPeqdpHbzb4sD3nnKmBrt7yb/H4XnMkmvWjhCmBkNfv59/Xq+PVzT8tMsyxWXAH8MpN9rukdw9VrzuUq4JdaLe0m0RErVlwJQIZgARmCBWQIFpAhWECGYAEZggVkCBaQIVhAhmABGYIFZAgWkCFYQIZgARmCBWQIFpAhWECGYAEZggVkCBaQIVhAhmABGYIFZAgWkCFYQIZgARmCBWQIFpAhWECGYAEZggVkCBaQIVhAhmABGYIFZAgWkCFYQIZgARmCBWQIFpAhWECGYAEZggVkCBaQIVhAhmABGYIFZAgWkCFYQIZgARmCBWQIFpAhWECGYAEZggVkCBaQIVhAhmABGYIFZAgWkCFYQIZgARmCBWQIFpAhWECGYAEZggVkCBaQIVhAhmABGYIFZAgWkCFYQIZgARmCBWQIFpAhWECGYAEZggVkCBaQIVhAhmABGYIFZAgWkCFYQIZgARmCBWQIFpAhWECGYAEZggVkCBaQIVhAhmABGYIFZAgWkCFYQIZgARmCBWQIFpAhWECGYAEZggVkCBaQIVhAhmABGYIFZAgWkCFYQIZgARmCBWQIFpAhWECGYAEZggVkCBaQIVhAhmABGYIFZAgWkCFYQIZgARmCBWQIFpAhWECGYAEZggVkCBaQIVhAhmABGYIFZAgWkCFYQMY/DfmID5+2XZIAAAAASUVORK5CYII=";
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>
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
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>
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.