Can't save an image inside <canvas> - javascript

I'm still new to this whole canvas things and there's something I have a problem with, namely, saving the content inside the canvas as image. Here's my fiddle
var img = new Image();
img.src = 'https://mdn.mozillademos.org/files/5397/rhino.jpg';
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
img.onload = function() {
ctx.drawImage(img, 0, 0);
img.style.display = 'none';
};
var link = document.createElement('a');
link.innerHTML = 'Save';
link.href = document.getElementById('canvas').toDataURL();
link.addEventListener('click', function(e) {
link.download = "imagename.png";
}, false);
document.body.appendChild(link);
<canvas id="canvas" width="300" height="300"></canvas>
While the save dialog appears just fine, the saved image is empty, like it doesn't capture the content at all.
Any help is appreciated, moreso if it's accompanied by on how/why my code doesn't work and your code will. Thanks.
Edit: Forgot to link the fiddle I base my code from here The difference is that that fiddle saves the drawing in the canvas while what I wrote is merely saving a static image from another source.

You need to set image href inside of onload and use img.crossOrigin = "anonymous" just to avoid cross origin error.
var img = new Image();
img.crossOrigin = "anonymous";
img.src = 'http://farm5.static.flickr.com/4005/4706825697_c0367e6dee_b.jpg';
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
img.onload = function() {
ctx.drawImage(img, 0, 0, 300, 300);
link.href = document.getElementById('canvas').toDataURL();
};
var link = document.createElement('a');
link.innerHTML = 'Save';
//link.href = document.getElementById('canvas').toDataURL();
link.addEventListener('click', function(e) {
link.download = "imagename.png";
}, false);
document.body.appendChild(link);
<canvas id="canvas" width="300" height="300"></canvas>

There are two issues.
The first is the image's origin. On JSFiddle you'll get an Uncaught DOMException: Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported.
As mentioned in another answer, Base64 encoding the image then setting img.src = 'data:image/jpeg;base64,/...'; resolves this problem.
Another solution: run a web server locally, and source the image by its location on the server, e.g. img.src = './rhino.jpg';.
Even after that's handled, the downloaded image will be "empty," as before. This is because of the order of execution of your code. link.href = canvas.toDataURL(); executes before the image's 'load' event fires, so the canvas is empty at the point you're setting the data.
Placing that line inside the img.onload function ensures that the link's download data is set only after the image has loaded. This approach is convenient, because it allows for the <a> tag to behave with default behavior once the href attribute is set (e.g. pointer on hover, underlined text by default).
Placing that line, instead, inside the link's 'click' event handler incidentally works, but you lose the default link behavior mentioned above.
Here's an example of the working JS, assuming the image file is on the server:
var img = new Image();
img.src = './rhino.jpg';
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var link = document.createElement('a');
link.innerHTML = 'Save';
img.onload = function() {
ctx.drawImage(img, 0, 0);
img.style.display = 'none';
link.href = canvas.toDataURL();
};
link.addEventListener('click', function(e) {
link.download = "imagename.png";
}, false);
document.body.appendChild(link);
And a JSFiddle, using the Base-64 encoded image.

Related

Download svg as png in react js

I tried to download the svg tag element, actually the image that the svg renders using the next function:
// Get the SVG element
const svg = document.getElementsByTagName('svg')[0];
const canvas = document.createElement('canvas');
canvas.width = svg.clientWidth;
canvas.height = svg.clientHeight;
const img = new Image();
img.src = `data:image/svg+xml;utf8,${new XMLSerializer().serializeToString(
svg,
)}`;
img.onload = function() {
console.log(img)
canvas.getContext('2d')?.drawImage(img, 0, 0);
};
const a = document.createElement('a');
a.download = 'my-image.png';
a.href = canvas.toDataURL();
a.click();
When i click on the dowsnload buttoon, the image is downloaded but it is black without any character. Why it is hapening and how to get a valid image?
PS: i investigated a lot of answer on the site but they don;t help. If someone will help with my example it will help me a lot.
The error occurs because you bring in an asynchronous context and don't wait for that and directly downloading the image.
You can avoid it by moving the "download part" (see example 1) into the onload function or by packing away the onload and hoping that it will load fast enough (example 2).
Example 1:
//...
img.onload = function() {
console.log(img)
canvas.getContext('2d')?.drawImage(img, 0, 0);
const a = document.createElement('a');
a.download = 'my-image.png';
a.href = canvas.toDataURL();
a.click();
};
Example 2:
//...
canvas.getContext('2d')?.drawImage(img, 0, 0);
const a = document.createElement('a');
a.download = 'my-image.png';
a.href = canvas.toDataURL();
a.click();

Download Image by clicking on button using JavaScript

I have this image with a data URL and a button.
Now, I want to download that image using JavaScript. So far I got this code but after download the image its saying: It looks like we don't support this format.
<img src="data:image/png;base64,iVBORw0KGgoA....." class="filterImage">
<input type="button" name="button" value="Save" id="saveImg" class="btn btn-primary float-right">
function saveImage() {
let image = document.querySelector(".filterImage");
var img = new Image();
img.onload = function() {
let canvas = document.createElement("canvas");
let width = canvas.width;
let height = canvas.height;
let context = canvas.getContext("2d");
context.drawImage( image, 0, 0, width, height );
}
img.src = image.src;
let link = document.createElement('a');
link.download = "image.png";
link.style.opacity = "0";
link.href = image.src;
document.body.append(link);
link.click();
link.remove();
}
saveImage(); // For testing purpose I am directly calling this function
Is there anything I am wrong? How can I solve it?
Some browsers do not allow links to be clicked programmatically (by way of ~link.click()); for example, when I try to do so, Chrome navigates to "#blocked".

Save canvas as png locally with javascript with Cross doimain

I want to save an image of the canvas tag, but when i try create an image from the canvas it tells me "SecurityError: The operation is insecure", which i believe is problems with the canvas coming from another domain than what im on. This canvas is a map which is generated with openlayers3.
var canvas = document.getElementsByClassName('ol-unselectable')[0];
var dataURL = canvas.toDataURL('image/png');
var window = window.open();
window.document.write('<img src='+dataURL+'/>');
I've also tried
canvas.toBlob(function(blob) {
saveAs(blob, 'map.png')
}
Is there an easy work around so the canvas is not tainted?
You tried write in new browser window from another window this is impossible without https protocol or localhost. But you can create new img in currently window.
var canvas = document.getElementsByClassName('ol-unselectable')[0];
var dataURL = canvas.toDataURL('image/png');
var img = new Image();
img.width = 1000;
img.height = 1000;
img.src = dataURL;
img.onload = function () {
document.body.append(img);
}

Html <img src=...> works but JS Image loading cause CORS error

I want to create picture editor in js+jquery. At the first step i ask user to give image url. But I come across problem when i try load image data inside JS (to generate base64 image uri). I get error in console: … has beeb blocked by CORS policy: Access-Control-Allow-Origin …. But I wonder why? If in html file i create for instance (image hotlink):
<img src="https://static.pexels.com/photos/87293/pexels-photo-87293.jpeg" />
The browser load image without any CORS problems ! Here is my JS code which for the same image throw CORS problem:
function downloadFile(url) {
console.log({url});
var img = new Image();
img.onload = function() {
console.log('ok');
// never execute because cors error
// … make base64 uri with image data needed for further processing
};
img.crossOrigin = "Anonymous";
img.src = url;
}
So the question is - how to force JS to load image (as html-tag load it) and convert it to base64 url avoiding CORS problem?
https://static.pexels.com/photos/87293/pexels-photo-87293.jpeg
I try to found solution my self (JS ES6) but find only-partially. We are able to load img from no-CORS support src into canvas but browser switch cavnas into 'taint mode' which not allow us to call toDataURL (and any other access to content).
function loadImgAsBase64(url, callback) {
let canvas = document.createElement('CANVAS');
let img = document.createElement('img');
//img.setAttribute('crossorigin', 'anonymous');
img.src = url;
img.onload = () => {
canvas.height = img.height;
canvas.width = img.width;
let context = canvas.getContext('2d');
context.drawImage(img, 0, 0);
let dataURL = canvas.toDataURL('image/png');
canvas = null;
callback(dataURL);
};
}
let url = 'http://lorempixel.com/500/150/sports/9/';
this.loadImgAsBase64(url, (dataURL) => {
msg.innerText = dataURL.slice(0,50)+'...';
});
IMAGE DATA: loading...<br>
<div id="msg"></div>
So only way to overcome this obstacle is to create proxy server (e.g. in PHP) which will have CORS 'on' and it will download images for given url and send back to our app in JS. I found some free server https://cors-anywhere.herokuapp.com which we can use to in development to tests. Below there is full functional code which return dataUri from given image url:
function loadImgAsBase64(url, callback) {
let canvas = document.createElement('CANVAS');
let img = document.createElement('img');
img.setAttribute('crossorigin', 'anonymous');
img.src = 'https://cors-anywhere.herokuapp.com/' + url;
img.onload = () => {
canvas.height = img.height;
canvas.width = img.width;
let context = canvas.getContext('2d');
context.drawImage(img, 0, 0);
let dataURL = canvas.toDataURL('image/png');
canvas = null;
callback(dataURL);
};
}
let url = 'http://lorempixel.com/500/150/sports/9/';
this.loadImgAsBase64(url, (dataURL) => {
msg.innerText = dataURL.slice(0,50)+'...';
// show pic
document.body.innerHTML += `<img src="${dataURL}">`
});
IMAGE DATA Loading...<br>
<div id="msg"></div>
Thats all :) (I tested it on chrome, firefox and safari)
I had a similar issue and my issue was solved when I passed query parameter as part of the image url.
sample:
http://example.com/image.jpeg?test=123
The only way to avoid CORS is to make changes to avoid cross-origin sharing, at least let the browser thinks that it is cross-origin.
Or, you have to modify the server to support CORS.

Convert SVG dataUrl to base64

I'm using a plugin (dom-to-image) to generate a SVG content from a div.
It returns me a dataURL like this:
data image/xml, charset utf-8, <svg...
If a put this on a <img src the image is shown to normally.
The intent is to grab this dataURL, convert it to base64 so I can save it as an image.png on a mobile app.
Is it possible?
I tryied this solution https://stackoverflow.com/a/28450879/1691609
But coudn't get to work.
The console fire an error about the dataUrl
TypeError: Failed to execute 'serializeToString' on 'XMLSerializer': parameter 1 is not of type 'Node'.
==== UPDATE :: PROBLEM EXPLANATION/HISTORY ====
I'm using Ionic Framework, so my project is an mobile app.
The dom-to-image is already working cause right now, its rendering a PNG through toPng function.
The problem is the raster PNG is a blurry.
So I thought: Maybe the SVG will have better quality.
And it IS!! Its 100% perfect, actually.
On Ionic, I'm using 2 step procedure to save the image.
After get the PNG generated by the dom-to-img(base64) dataURL, I convert it to a Blob and then save into device.
This is working, but the final result, as I said, is blurry.
Then with SVG maybe it will be more "high quality" per say.
So, in order to do "minimal" change on a process that s already working :D I just need to convert an SVG into base64 dataURL....
Or, as some of you explained to me, into something else, like canvas...
I don't know any much :/
===
Sorry for the long post, and I really, really thank your help guys!!
EDIT COUPLE OF YARS LATER
Use JS fiddle for a working example: https://jsfiddle.net/msb42ojx/
Note, if you don't own DOM content (images), and those images don't have CORS enabled for everyone (Access-Control-Allow-Origin header), canvas cant render those images
I'm not trying to find out why is your case not working, here is how I did when I had something similar to do:
get the image sourcce (dom-to-image result)
set up a canvas with that image inside (using the image source)
download the image from canvas in whatever image you like: png, jpeg whatever
by the way you can resize the image to a standard format
document.getElementById('mydownload').onclick= function(){
var wrapper = document.getElementById('wrapper');
//dom to image
domtoimage.toSvg(wrapper).then(function (svgDataUrl) {
//download function
downloadPNGFromAnyImageSrc(svgDataUrl);
});
}
function downloadPNGFromAnyImageSrc(src)
{
//recreate the image with src recieved
var img = new Image;
//when image loaded (to know width and height)
img.onload = function(){
//drow image inside a canvas
var canvas = convertImageToCanvas(img);
//get image/png from convas
var pngImage = convertCanvasToImage(canvas);
//download
var anchor = document.createElement('a');
anchor.setAttribute('href', pngImage.src);
anchor.setAttribute('download', 'image.png');
anchor.click();
};
img.src = src;
// Converts image to canvas; returns new canvas element
function convertImageToCanvas(image) {
var canvas = document.createElement("canvas");
canvas.width = image.width;
canvas.height = image.height;
canvas.getContext("2d").drawImage(image, 0, 0);
return canvas;
}
// Converts canvas to an image
function convertCanvasToImage(canvas) {
var image = new Image();
image.src = canvas.toDataURL("image/png");
return image;
}
}
#wrapper{
background: red;
color: blue;
}
<script src="https://rawgit.com/tsayen/dom-to-image/master/src/dom-to-image.js"></script>
<button id='mydownload'>Download DomToImage</button>
<div id="wrapper">
<img src="http://i.imgur.com/6GvKdxY.jpg"/>
<div> DUDE IS WORKING</div>
<img src="http://i.imgur.com/6GvKdxY.jpg"/>
</div>
I translated #SilentTremor's solution into React/JS-Class:
class SVGToPNG {
static convert = function (src) {
var img = new Image();
img.onload = function () {
var canvas = SVGToPNG.#convertImageToCanvas(img);
var pngImage = SVGToPNG.#convertCanvasToImage(canvas);
var anchor = document.createElement("a");
anchor.setAttribute("href", pngImage.src);
anchor.setAttribute("download", "image.png");
anchor.click();
};
img.src = src;
};
static #convertImageToCanvas = function (image) {
var canvas = document.createElement("canvas");
canvas.width = image.width;
canvas.height = image.height;
canvas.getContext("2d").drawImage(image, 0, 0);
return canvas;
};
static #convertCanvasToImage = function (canvas) {
var image = new Image();
image.src = canvas.toDataURL("image/png");
return image;
};
}
export default SVGToPNG;
Usage:
let dataUrl = someCanvas.toDataURL("image/svg+xml");
SVGToPNG.convert(dataUrl);

Categories

Resources