Overlay 2x image blobs - javascript

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

Related

SVG to Image returning blank image blob

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);

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>

Convert SVG to PNG/JPEG with custom width and height

I have a SVG code with width and height. I want to download this SVG in PNG and JPEG Format with custom width and Height.
I have tried HTML canvas approach to achieve this but when canvas draws image it crops out the SVG.
Here is the Code
SVG Code
<svg id="svgcontent" width="640" height="480" x="640" y="480" overflow="visible" xmlns="http://www.w3.org/2000/svg" xmlns:se="http://svg-edit.googlecode.com" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 640 480"><!-- Created with SVG-edit - https://github.com/SVG-Edit/svgedit--><g class="layer" style="pointer-events:all"><title style="pointer-events:inherit">Layer 1</title><ellipse fill="#FF0000" stroke="#000000" stroke-width="5" cx="280.5" cy="235.5" rx="217" ry="198" id="svg_1"></ellipse></g></svg>
JavaScript Function for conversion of SVG to png/jpeg
function save() {
// Converting SVG to String
var stringobJ = new XMLSerializer();
var svg = document.getElementById('svgcontent');
var svgString = stringobJ .serializeToString(svg );
// IE9 doesn't allow standalone Data URLs
svg = '<?xml version="1.0"?>\n' + svgString ;
// Creating an Image Element
var image = new Image();
image.src = 'data:image/svg+xml;base64,' + btoa(svg);
image.width = 300; // This doesn't have any effect
image.height = 150; // This doesn't have any effect
// Creating Canvas Element
var canvas = document.createElement('canvas');
var context = canvas.getContext('2d');
image.onload = function() {
context.drawImage(image, 0, 0);
var a = document.createElement('a');
a.download = "image.png"; //Saving in PNG
a.href = canvas.toDataURL('image/png'); //Saving in PNG
a.style = 'display: none;';
a.click();
}
}
It gives me Imgae in PNG format but its not complete image of SVG its just the part of image according to width of canvas bcz canvas draws image from top right corner of image and it goes on drawing image till width and height of canvas.
By default canvas width is 300 and height is 150
So if canvas width and height is not give its just outputs and image of 300x150.
I have tried canvas.width = anyvalue;
canvas.height= anyvalue;
but it doesn't effect the output
what i want is
that no matter what is the dimensions of SVG
when user gives width and height the SVG should completely fit in canvas
This is the Actual SVG and this actually needed on download with all white background and image
this is this is the output but i want full image with these dimensions
Giving width and height to canvas as actual SVG have is not a solution to my problem..... width and height of canvas is dynamic
jsfiddle link to the problem
I've made a few changes:
your svg viewBox="0 0 640 480". This defines the size of the SVG canvas. When you draw the image (unless you want to crop it) you want to keep the height proportional, i.e. if you want the width to be 300 the height should be 225.
Then when you create a new canvas element you need to declare the width and the height of the canvas element canvas.width = image.width, canvas.height = image.height,before drawing the image.
function save() {
// Converting SVG to String
var stringobJ = new XMLSerializer();
var svg = document.getElementById('svgcontent');
var svgString = stringobJ .serializeToString(svg );
// IE9 doesn't allow standalone Data URLs
svg = '<?xml version="1.0"?>\n' + svgString ;
// Creating an Image Element
var image = new Image();
image.src = 'data:image/svg+xml;base64,' + btoa(svg);
image.width = 300;
image.height = 480*image.width / 640; // keep the height proportional
// Creating Canvas Element
var canvas = document.createElement('canvas');
var context = canvas.getContext('2d');
image.onload = function() {
canvas.width = image.width,canvas.height = image.height,
context.drawImage(image, 0, 0);
var a = document.createElement('a');
a.download = "image.png"; //Saving in PNG
a.href = canvas.toDataURL('image/png'); //Saving in PNG
a.style = 'display: none;';
a.click();
}
}
//save()
<svg id="svgcontent" viewBox="0 0 640 480" xmlns="http://www.w3.org/2000/svg" xmlns:se="http://svg-edit.googlecode.com" xmlns:xlink="http://www.w3.org/1999/xlink"><!-- Created with SVG-edit - https://github.com/SVG-Edit/svgedit-->
<g class="layer" style="pointer-events:all">
<title style="pointer-events:inherit">Layer 1</title>
<ellipse fill="#FF0000" stroke="#000000" stroke-width="5" cx="280.5" cy="235.5" rx="217" ry="198" id="svg_1"></ellipse>
</g>
</svg>

jsPDF addImage not working

I am using jsPDF to create a PDF by looping through some HTML code to find the values it needs. I am able to get the values just fine, but when I try to insert the header_img it does not work. I have found many solutions on Google but the one I am working with now is the only one that does not throw an error.
This is the code being used to get take the url that is provided via the loop, convert it to a DataURL, and then insert the image into the PDF. It does not give me any errors, but all that is in the PDF is the black border and no image. Any ideas?
function getDataUri(url, cb) {
var image = new Image();
image.setAttribute('crossOrigin', 'anonymous'); //getting images from external domain
image.onload = function () {
console.log(url);
var canvas = document.createElement('canvas');
canvas.width = this.naturalWidth;
canvas.height = this.naturalHeight;
//next three lines for white background in case png has a transparent background
var ctx = canvas.getContext('2d');
ctx.fillStyle = '#fff'; /// set white fill style
ctx.fillRect(0, 0, canvas.width, canvas.height);
canvas.getContext('2d').drawImage(this, 0, 0);
cb(canvas.toDataURL('image/jpeg'));
};
image.src = url;
}
var pdf = new jsPDF('p', 'in', 'letter');
var left_margin = .5;
var top_margin =.5;
var height = 2.313;
var width = 3.25;
var header_img_height = 1;
//Draw border
pdf.setLineWidth(1/92);
pdf.setDrawColor(0, 0, 0);
pdf.rect(left_margin, top_margin, width, height);
//example image
var header_img = 'http://www.quotehd.com/imagequotes/authors24/jeff-olsen-quote-two-clicks-and-its-broke.jpg';
let logo = null;
//callback to get DataURL
getDataUri(header_img, function(dataUri) {
logo = dataUri;
console.log("logo=" + logo);
//Add image to PDF
pdf.addImage(logo, 'JPEG', left_margin, top_margin, header_img_height, width);
});
pdf.output('save', 'test.pdf');
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/1.4.1/jspdf.debug.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
I ended up solving the issue by writing a PHP script that would convert the image to Base64.
$img_src = urldecode($_GET['url']);
$img_str = base64_encode(file_get_contents($img_src));
echo 'data:' . getimagesize($img_src)['mime'] . ';base64,' . $img_str;
Then using jQuery, I passed the url to the PHP script and got the Base64 data in return:
function getBase64(url) {
var out = '';
$.ajax({
url: 'get-dataurl.php?url=' + encodeURI(url),
async: false,
dataTye: 'text',
success: function(data) {
out = data;
}
});
return out;
}
Certainly was not ideal, but it works.
You can encode the image to base64, you would use this tool or any similar and replace it on your code.

broken image generated by using highcharts svg

I am trying to generate image of my charts from my local server(I've installed highcharts on it).
Following is the code
var chart = Highcharts.charts[i];
var render_width = chart.chartWidth;
var render_height = chart.chartHeight;
// Get the cart's SVG code
var svg = chart.getSVG({
exporting: {
sourceWidth: chart.chartWidth,
sourceHeight: chart.chartHeight
}
});
// Create a canvas
var canvas = document.createElement('canvas');
canvas.height = render_height;
canvas.width = render_width;
document.body.appendChild(canvas);
// Create an image and draw the SVG onto the canvas
var image = new Image;
image.onload = function() {
canvas.getContext('2d').drawImage(this, 0, 0, render_width, render_height);
console.log(image.src);
afterPlotExport();
};
image.src = 'data:image/svg+xml;base64,' + window.btoa(svg);
}
When i'm trying to log inside onload() function i'm expecting a base64 string.But the string which is generated appears as broken image when i copy paste it to an online base64 to image converter.
Any help would be appreciated as to why the image is appearing broken?

Categories

Resources