Over the past few months, I have been working on this code that deals with ellipsoidal earth and just recently I have finished it. My professor now wants me to send him pictures of the diagrams I made as an SVG file. I know in Python you can put a few lines of code into your project to have it download the image, but I am unsure how it works in JavaScript. How can I do this in JavaScript, or is there another easier way?
Check out this demo: https://codepen.io/Alexander9111/pen/VwLaaPe?editors=1010
Code (modified from https://stackoverflow.com/a/19885344/9792594):
function downloadSVGasTextFile() {
const base64doc = btoa(unescape(encodeURIComponent(document.querySelector('svg').outerHTML)));
const a = document.createElement('a');
const e = new MouseEvent('click');
a.download = 'download.svg';
a.href = 'data:text/html;base64,' + base64doc;
a.dispatchEvent(e);
}
downloadSVGasTextFile();
You encode your svg as a 64bit string and then use that as the href of an anchor tag and then dispatch a click event to download the svg text file as download.svg
If you want to save an image, such as .png then you can first draw svg image into a cnavas then download that as .png,
UPDATE
I modified the answer and the demo to now include both download as text file .svg or download as image file .png https://codepen.io/Alexander9111/pen/VwLaaPe:
HTML:
<svg>
...
</svg>
<br/>
<button id="downloadPNG">Download .png</button>
<button id="downloadSVG">Download .svg</button>
JS:
function downloadSVGAsText() {
const svg = document.querySelector('svg');
const base64doc = btoa(unescape(encodeURIComponent(svg.outerHTML)));
const a = document.createElement('a');
const e = new MouseEvent('click');
a.download = 'download.svg';
a.href = 'data:image/svg+xml;base64,' + base64doc;
a.dispatchEvent(e);
}
function downloadSVGAsPNG(e){
const canvas = document.createElement("canvas");
const svg = document.querySelector('svg');
const base64doc = btoa(unescape(encodeURIComponent(svg.outerHTML)));
const w = parseInt(svg.getAttribute('width'));
const h = parseInt(svg.getAttribute('height'));
const img_to_download = document.createElement('img');
img_to_download.src = 'data:image/svg+xml;base64,' + base64doc;
console.log(w, h);
img_to_download.onload = function () {
canvas.setAttribute('width', w);
canvas.setAttribute('height', h);
const context = canvas.getContext("2d");
//context.clearRect(0, 0, w, h);
context.drawImage(img_to_download,0,0,w,h);
const dataURL = canvas.toDataURL('image/png');
if (window.navigator.msSaveBlob) {
window.navigator.msSaveBlob(canvas.msToBlob(), "download.png");
e.preventDefault();
} else {
const a = document.createElement('a');
const my_evt = new MouseEvent('click');
a.download = 'download.png';
a.href = dataURL;
a.dispatchEvent(my_evt);
}
//canvas.parentNode.removeChild(canvas);
}
}
const downloadSVG = document.querySelector('#downloadSVG');
downloadSVG.addEventListener('click', downloadSVGAsText);
const downloadPNG = document.querySelector('#downloadPNG');
downloadPNG.addEventListener('click', downloadSVGAsPNG);
Related
I'm attempting to put a watermark image on an input image before saving it to a database. I'm using a canvas to do this then convert the canvas back to a blob but when I reference the watermark image in the projects local file directory to draw onto the canvas, I get a "Tainted canvases may not be exported." error in the console.
// Read input file
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => {
// Create canvas & calculate Stamp size and position
const canvas = document.createElement("canvas");
canvas.width = canvasSize.width;
canvas.height = canvasSize.height;
var calcW = canvasSize.width/2;
var calcH = calcW/2.16;
var calcX = (canvasSize.width - calcW)/2;
var calcY = (canvasSize.height - calcH)/2;
// Load input image into canvas
const img = document.createElement("img");
img.src = reader.result;
img.onload = () => {
const ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0);
//Load & draw the Stamp image
const mark = new Image();
mark.src = 'assets/img/compliant.png'; // <<TAINTED ASSET?
mark.onload = () => {
ctx.drawImage(mark, calcX,calcY,calcW,calcH);
// Convert to blob and get URL
canvas.toBlob((canvasBlob) => {
const newImg = document.createElement("img"),
url = URL.createObjectURL(canvasBlob);
newImg.onload = function () {
//Destroy objecturl after image loading
URL.revokeObjectURL(url);
};
// Show stamped image
newImg.src = url;
window.open(url);
});
};
};
};
});
You have to use
img.crossOrigin='anonymous';
On every image you want to manipulate (draw in a canvas, transform to an array buffer… this is to prevent you from manipulating potentially private user data)
https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video#attr-crossorigin
I am developing angular application and trying to decode image with QR code on client side only and facing with next errors.
I have next flow:
User uploads image.
I decode qr code from image.
<input type="file" name="file" id="file" accept="image/*"(change)="qrCodeUploaded($event.target.files)"/>
I have tried next libraries:
https://github.com/zxing-js/library
qrCodeUploaded(files: FileList): void {
const fileReader = new FileReader();
const codeReader = new BrowserQRCodeReader();
fileReader.readAsDataURL(files[0]);
fileReader.onload = (e: any) => {
var image = document.createElement("img");
image.src = e.target.result;
setTimeout(() => codeReader.decodeFromImage(image, e.target.result).then(res => console.log(res)), 100);
};
}
Works for some of qr codes, but issues on mobile. If you will take a photo of QR code with your phone, it will be not decoded. So for mobile you will need to implement video.
https://github.com/cozmo/jsQR
qrCodeUploaded(files: FileList): void {
const fileReader = new FileReader();
fileReader.readAsArrayBuffer(files[0]);
fileReader.onload = (e: any) => {
console.log(new Uint8ClampedArray(e.target.result));
console.log(jsQR(new Uint8ClampedArray(e.target.result), 256, 256));
};
}
I get next error for any QR images I upload:
core.js:15714 ERROR Error: Malformed data passed to binarizer.
at Object.binarize (jsQR.js:408)
at jsQR (jsQR.js:368)
gist link:
https://gist.github.com/er-ant/b5c75c822eb085e70035cf333bb0fb55
Please, tell me what I am doing wrong and propose some solution for QR codes decoding. Open for any thoughts :)
For second library I missed that it expects ImageData and I pass Binary Data.
Thus, we have 3 solutions how to convert Binary Data to ImageData:
Use createImageBitmap Kaiido solution with some updates, as proposed in comments doesn't work.
qrCodeUploadedHandler(files: FileList): void {
const file: File = files[0];
createImageBitmap(files[0])
.then(bmp => {
const canvas = document.createElement('canvas');
const width: number = bmp.width;
const height: number = bmp.height;
canvas.width = bmp.width;
canvas.height = bmp.height;
const ctx = canvas.getContext('2d');
ctx.drawImage(bmp, 0, 0);
const qrCodeImageFormat = ctx.getImageData(0,0,bmp.width,bmp.height);
const qrDecoded = jsQR(qrCodeImageFormat.data, qrCodeImageFormat.width, qrCodeImageFormat.height);
});
}
Get ImageData from canvas.
qrCodeUploadedHandler(files: FileList): void {
const file: File = files[0];
const fileReader: FileReader = new FileReader();
fileReader.readAsDataURL(files[0]);
fileReader.onload = (event: ProgressEvent) => {
const img: HTMLImageElement = new Image();
img.onload = () => {
const canvas: HTMLCanvasElement = document.createElement('canvas');
const width: number = img.width;
const height: number = img.height;
canvas.width = width;
canvas.height = height;
const canvasRenderingContext: CanvasRenderingContext2D = canvas.getContext('2d');
console.log(canvasRenderingContext);
canvasRenderingContext.drawImage(img, 0, 0);
const qrCodeImageFormat: ImageData = canvasRenderingContext.getImageData(0, 0, width, height);
const qrDecoded = jsQR(qrCodeImageFormat.data, qrCodeImageFormat.width, qrCodeImageFormat.height);
canvas.remove();
};
img.onerror = () => console.error('Upload file of image format please.');
img.src = (<any>event.target).result;
}
You can parse images with png.js and jpeg-js libraries for ImageData.
step:
Create a new image object from image file
Create a canvas
Draw the image to the canvas
Get ImageData through the context of canvas
Scan QR code with jsQR.
install:
npm install jsqr --save
code:
// utils/qrcode.js
import jsQR from "jsqr"
export const scanQrCode = (file, callback) => {
const image = new Image()
image.src = file.content
image.addEventListener("load", (e) => {
console.log(
"image on load, image.naturalWidth, image.naturalHeight",
image.naturalWidth,
image.naturalHeight
)
const canvas = document.createElement("canvas") // Creates a canvas object
canvas.width = image.naturalWidth // Assigns image's width to canvas
canvas.height = image.naturalHeight // Assigns image's height to canvas
const context = canvas.getContext("2d") // Creates a contect object
context.imageSmoothingEnabled = false
context.drawImage(image, 0, 0, image.naturalWidth, image.naturalHeight) // Draws the image on canvas
const imageData = context.getImageData(0, 0, image.naturalWidth, image.naturalHeight) // Assigns image base64 string in jpeg format to a variable
const code = jsQR(imageData.data, image.naturalWidth, image.naturalHeight)
if (code) {
console.log("Found QR code", code)
callback(code)
}
})
}
use:
scanQrCode(file, (code) => {
console.log(code.data);
});
This question already has answers here:
Download Canvas as PNG in fabric.js giving network Error
(5 answers)
Is it possible to programmatically detect size limit for data url?
(1 answer)
Closed 5 years ago.
Good day. I use the following code to save canvas to local image file.
let canvas = document.createElement('canvas');
canvas.width = "1056";
canvas.height = "1248";
document.body.appendChild(canvas);//in case of debug
rasterizeHTML.drawHTML(document.getElementById("canvas").outerHTML, canvas).then(function() {
date = new Date();
let a = document.createElement('a');
let bitmap = canvas.toDataURL("image/png");
a.href = bitmap.replace("image/png", "image/octet-stream");
a.download = "canvas " + date.valueOf()+'.png';
a.click();
});
It works well until canvas height reaches 1k, right after that the resulting file stops loading with error, the file name also corrupts. What could possibly go wrong? Note that save works fine with small images (like 500x500 or 700x900). The canvas rendering works with whatever resolution I set, I can see it by appended image.
Can you post more of your code? It works for me in this snippet.
var size = 2000;
const canvas = document.getElementById('c');
var c = canvas.getContext('2d');
canvas.width = size;
canvas.height = size;
canvas.style.width = size+'px';
canvas.style.height = size+'px';
// fill canvas with color
c.fillStyle = '#123456';
c.fillRect(0, 0, size, size);
const save = document.getElementById('save');
save.onclick = saveCanvas;
function saveCanvas() {
try {
var date = new Date();
const a = document.createElement('a');
const bitmap = canvas.toDataURL("image/png");
a.href = bitmap.replace("image/png", "image/octet-stream");
a.download = "canvas " + date.valueOf() + '.png';
a.click();
} catch (ex) {
alert(ex);
}
}
<a id="save" href="#">save</a>
<canvas id="c"></canvas>
I want to make it so that when I press the save button, file explorer opens and opts me to choose location to save the JSON file of the canvas. I also want to be able to load the canvas with the JSON file via the load button. How can I get started with this? Any help is appreciated.
I hope this is what you are trying to achieve:
var canvas = document.querySelector('canvas')
var ctx = canvas.getContext('2d');
var reader = new FileReader();
// generates a random RGB color string
var randomColor = function () {
return `rgb(${Math.random() * 255}, ${Math.random() * 255}, ${Math.random() * 255})`;
}
// draw something on the canvas
ctx.fillStyle = randomColor();
ctx.fillRect(Math.random() * 100, 100, 100, Math.random() * 150);
ctx.fillStyle = randomColor();
ctx.fillRect(Math.random() * 200, Math.random() * 50, Math.random() * 150, 200);
// event handler for the save button
document.getElementById('save').addEventListener('click', function () {
// retrieve the canvas data
var canvasContents = canvas.toDataURL(); // a data URL of the current canvas image
var data = { image: canvasContents, date: Date.now() };
var string = JSON.stringify(data);
// create a blob object representing the data as a JSON string
var file = new Blob([string], {
type: 'application/json'
});
// trigger a click event on an <a> tag to open the file explorer
var a = document.createElement('a');
a.href = URL.createObjectURL(file);
a.download = 'data.json';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
});
// event handler for the load button
document.getElementById('load').addEventListener('change', function () {
if (this.files[0]) {
// read the contents of the first file in the <input type="file">
reader.readAsText(this.files[0]);
}
});
// this function executes when the contents of the file have been fetched
reader.onload = function () {
var data = JSON.parse(reader.result);
var image = new Image();
image.onload = function () {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(image, 0, 0); // draw the new image to the screen
}
image.src = data.image; // data.image contains the data URL
};
<canvas height="300" width="300"></canvas>
<div><button id="save">Save</button></div>
<div>Load: <input type="file" id="load"></div>
I have svg-png almost working perfectly client side, just in javascript/d3 it all works, but it loses large amounts of detail. Can anyone shed light on why this might be?:
The original image is:
var svg = document.querySelector( "svg" );
var svgData = new XMLSerializer().serializeToString( svg );
var canvas = document.createElement("canvas");
canvas.width = d3.select("svg").attr("width");
canvas.height = d3.select("svg").attr("height");
ctx = canvas.getContext("2d");
var img = document.createElement( "img" );
img.setAttribute( "src", "data:image/svg+xml;base64," + btoa( svgData ) );
img.onload = function() {
ctx.drawImage( img, 0, 0 );
var canvasdata = canvas.toDataURL("image/png");
console.log(canvasdata)
var pngimg = '<img src="'+canvasdata+'">';
d3.select("#pngdataurl").html(pngimg);
var a = document.createElement("a");
a.download = "name"+".png";
a.href = canvasdata;
console.log(a.click())
};
The output of that is:
I also tried:
var html = d3.select("svg")
.attr("version", 1.1)
.attr("xmlns", "http://www.w3.org/2000/svg")
.node().parentNode.innerHTML;
var width = d3.select("svg").attr("width");
var height = d3.select("svg").attr("height");
image.src = 'data:image/svg+xml;base64,'+ btoa(unescape(encodeURIComponent(html)));
but that fails - on the image.onload() I get "HTMLImageElement provided is in the 'broken' state"
Saving an SVG to PNG in the browser will ignore all linked CSS styles applied to the SVG elements.
If you are using d3.js you can add the styles directly to the elements of a particular class using:
d3.selectAll(".mg-main-area").style("color","red");
For more details see this article, which I wrote about rasterising SVG in the browser.
From the comments it seems you have library applying the CSS, so if you don't want to override the styles manually using d3, you could use jQuery code like the below to pull all CSS elements from a class and apply them as inline styles:
$('.mg-main-area').each(function(i, e) {
var st = e.style;
var props = [];
for(var prop in st) {
if($(this).css(prop)) {
props.push(prop + ':' + $(this).css(prop));
}
}
this.style.cssText = props.join(';');
$(this).children().makeCssInline();
});