Earlier this day, I have succeed in converting SVG file to JPEG using javascript. The main steps are:
Get SVG image from a url
Add image to HTML5 Canvas
Convert the Canvas to JPEG encoded in base64
I replicate the getImageFromUrl function on jsPDF-master to achieve this.
var getImageFromUrl = function (url, callback) {
var img = new Image,
data, ret = {
data: null,
pending: true
};
img.onError = function () {
throw new Error('Cannot load image: "' + url + '"');
}
img.onload = function () {
var canvas = document.createElement('canvas');
document.body.appendChild(canvas);
canvas.width = img.width;
canvas.height = img.height;
var ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0);
// Grab the image as a jpeg encoded in base64, but only the data
data = canvas.toDataURL('image/jpeg').slice('data:image/jpeg;base64,'.length);
// Convert the data to binary form
data = atob(data)
document.body.removeChild(canvas);
ret['data'] = data;
ret['pending'] = false;
if (typeof callback === 'function') {
callback(data);
}
}
img.src = url;
return ret;
}
From that function, the image to be converted is actually a file. In my case, I don't have a file but only the raw code (text when you open a SVG file with text editor).
My question is:
How do you add raw code of a SVG file into the HTML canvas? Is this process also have .onload event attribute like image object?
Thank you
You can convert a "raw" (inline) SVG to image by converting it to a Blob and then use that as an image source:
function drawInlineSVG(ctx, rawSVG, callback) {
var
/// create Blob of inlined SVG
svg = new Blob([rawSVG], {type:"image/svg+xml;charset=utf-8"}),
/// create URL (handle prefixed version)
domURL = self.URL || self.webkitURL || self,
url = domURL.createObjectURL(svg),
/// create Image
img = new Image;
/// handle image loading
img.onload = function () {
/// draw SVG to canvas
ctx.drawImage(this, 0, 0);
domURL.revokeObjectURL(url);
callback(this);
};
img.src = url;
}
Then call it like this:
var rawSVG = '<svg ... >';
drawInlineSVG(ctx, rawSVG, function(img) {
console.log('done!');
});
An error handler should of course be included for production code (not shown here).
Important to note: You cannot draw inline SVG's if they contain external references (CSS styles, images and so on). This is due to browser's security policies. You would have to convert all external references to inline data (ie. images to data-uris and so on).
Related
I have a regular HTML page with some images (just regular <img /> HTML tags). I'd like to get their content, base64 encoded preferably, without the need to redownload the image (ie. it's already loaded by the browser, so now I want the content).
I'd love to achieve that with Greasemonkey and Firefox.
Note: This only works if the image is from the same domain as the page, or has the crossOrigin="anonymous" attribute and the server supports CORS. It's also not going to give you the original file, but a re-encoded version. If you need the result to be identical to the original, see Kaiido's answer.
You will need to create a canvas element with the correct dimensions and copy the image data with the drawImage function. Then you can use the toDataURL function to get a data: url that has the base-64 encoded image. Note that the image must be fully loaded, or you'll just get back an empty (black, transparent) image.
It would be something like this. I've never written a Greasemonkey script, so you might need to adjust the code to run in that environment.
function getBase64Image(img) {
// Create an empty canvas element
var canvas = document.createElement("canvas");
canvas.width = img.width;
canvas.height = img.height;
// Copy the image contents to the canvas
var ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0);
// Get the data-URL formatted image
// Firefox supports PNG and JPEG. You could check img.src to
// guess the original format, but be aware the using "image/jpg"
// will re-encode the image.
var dataURL = canvas.toDataURL("image/png");
return dataURL.replace(/^data:image\/(png|jpg);base64,/, "");
}
Getting a JPEG-formatted image doesn't work on older versions (around 3.5) of Firefox, so if you want to support that, you'll need to check the compatibility. If the encoding is not supported, it will default to "image/png".
This Function takes the URL then returns the image BASE64
function getBase64FromImageUrl(url) {
var img = new Image();
img.setAttribute('crossOrigin', 'anonymous');
img.onload = function () {
var canvas = document.createElement("canvas");
canvas.width =this.width;
canvas.height =this.height;
var ctx = canvas.getContext("2d");
ctx.drawImage(this, 0, 0);
var dataURL = canvas.toDataURL("image/png");
alert(dataURL.replace(/^data:image\/(png|jpg);base64,/, ""));
};
img.src = url;
}
Call it like this :
getBase64FromImageUrl("images/slbltxt.png")
Coming long after, but none of the answers here are entirely correct.
When drawn on a canvas, the passed image is uncompressed + all pre-multiplied.
When exported, its uncompressed or recompressed with a different algorithm, and un-multiplied.
All browsers and devices will have different rounding errors happening in this process
(see Canvas fingerprinting).
So if one wants a base64 version of an image file, they have to request it again (most of the time it will come from cache) but this time as a Blob.
Then you can use a FileReader to read it either as an ArrayBuffer, or as a dataURL.
function toDataURL(url, callback){
var xhr = new XMLHttpRequest();
xhr.open('get', url);
xhr.responseType = 'blob';
xhr.onload = function(){
var fr = new FileReader();
fr.onload = function(){
callback(this.result);
};
fr.readAsDataURL(xhr.response); // async call
};
xhr.send();
}
toDataURL(myImage.src, function(dataURL){
result.src = dataURL;
// now just to show that passing to a canvas doesn't hold the same results
var canvas = document.createElement('canvas');
canvas.width = myImage.naturalWidth;
canvas.height = myImage.naturalHeight;
canvas.getContext('2d').drawImage(myImage, 0,0);
console.log(canvas.toDataURL() === dataURL); // false - not same data
});
<img id="myImage" src="https://dl.dropboxusercontent.com/s/4e90e48s5vtmfbd/aaa.png" crossOrigin="anonymous">
<img id="result">
A more modern version of kaiido's answer using fetch would be:
function toObjectUrl(url) {
return fetch(url)
.then((response)=> {
return response.blob();
})
.then(blob=> {
return URL.createObjectURL(blob);
});
}
https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch
Edit: As pointed out in the comments this will return an object url which points to a file in your local system instead of an actual DataURL so depending on your use case this might not be what you need.
You can look at the following answer to use fetch and an actual dataURL: https://stackoverflow.com/a/50463054/599602
shiv / shim / sham
If your image(s) are already loaded (or not), this "tool" may come in handy:
Object.defineProperty
(
HTMLImageElement.prototype,'toDataURL',
{enumerable:false,configurable:false,writable:false,value:function(m,q)
{
let c=document.createElement('canvas');
c.width=this.naturalWidth; c.height=this.naturalHeight;
c.getContext('2d').drawImage(this,0,0); return c.toDataURL(m,q);
}}
);
.. but why?
This has the advantage of using the "already loaded" image data, so no extra request is needed. Additionally it lets the end-user (programmer like you) decide the CORS and/or mime-type and quality -OR- you can leave out these arguments/parameters as described in the MDN specification here.
If you have this JS loaded (prior to when it's needed), then converting to dataURL is as simple as:
examples
HTML
<img src="/yo.jpg" onload="console.log(this.toDataURL('image/jpeg'))">
JS
console.log(document.getElementById("someImgID").toDataURL());
GPU fingerprinting
If you are concerned about the "preciseness" of the bits then you can alter this tool to suit your needs as provided by #Kaiido's answer.
its 2022, I prefer to use modern createImageBitmap() instead of onload event.
*note: image should be same origin or CORS enabled
async function imageToDataURL(imageUrl) {
let img = await fetch(imageUrl);
img = await img.blob();
let bitmap = await createImageBitmap(img);
let canvas = document.createElement("canvas");
let ctx = canvas.getContext("2d");
canvas.width = bitmap.width;
canvas.height = bitmap.height;
ctx.drawImage(bitmap, 0, 0, bitmap.width, bitmap.height);
return canvas.toDataURL("image/png");
// image compression?
// return canvas.toDataURL("image/png", 0.9);
};
(async() => {
let dataUrl = await imageToDataURL('https://en.wikipedia.org/static/images/project-logos/enwiki.png')
wikiImg.src = dataUrl;
console.log(dataUrl)
})();
<img id="wikiImg">
Use onload event to convert image after loading
function loaded(img) {
let c = document.createElement('canvas')
c.getContext('2d').drawImage(img, 0, 0)
msg.innerText= c.toDataURL();
}
pre { word-wrap: break-word; width: 500px; white-space: pre-wrap; }
<img onload="loaded(this)" src="https://cors-anywhere.herokuapp.com/http://lorempixel.com/200/140" crossorigin="anonymous"/>
<pre id="msg"></pre>
This is all you need to read.
https://developer.mozilla.org/en-US/docs/Web/API/FileReader/readAsBinaryString
var height = 200;
var width = 200;
canvas.width = width;
canvas.height = height;
var ctx = canvas.getContext('2d');
ctx.strokeStyle = '#090';
ctx.beginPath();
ctx.arc(width/2, height/2, width/2 - width/10, 0, Math.PI*2);
ctx.stroke();
canvas.toBlob(function (blob) {
//consider blob is your file object
var reader = new FileReader();
reader.onload = function () {
console.log(reader.result);
}
reader.readAsBinaryString(blob);
});
In HTML5 better use this:
{
//...
canvas.width = img.naturalWidth; //img.width;
canvas.height = img.naturalHeight; //img.height;
//...
}
I have a regular HTML page with some images (just regular <img /> HTML tags). I'd like to get their content, base64 encoded preferably, without the need to redownload the image (ie. it's already loaded by the browser, so now I want the content).
I'd love to achieve that with Greasemonkey and Firefox.
Note: This only works if the image is from the same domain as the page, or has the crossOrigin="anonymous" attribute and the server supports CORS. It's also not going to give you the original file, but a re-encoded version. If you need the result to be identical to the original, see Kaiido's answer.
You will need to create a canvas element with the correct dimensions and copy the image data with the drawImage function. Then you can use the toDataURL function to get a data: url that has the base-64 encoded image. Note that the image must be fully loaded, or you'll just get back an empty (black, transparent) image.
It would be something like this. I've never written a Greasemonkey script, so you might need to adjust the code to run in that environment.
function getBase64Image(img) {
// Create an empty canvas element
var canvas = document.createElement("canvas");
canvas.width = img.width;
canvas.height = img.height;
// Copy the image contents to the canvas
var ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0);
// Get the data-URL formatted image
// Firefox supports PNG and JPEG. You could check img.src to
// guess the original format, but be aware the using "image/jpg"
// will re-encode the image.
var dataURL = canvas.toDataURL("image/png");
return dataURL.replace(/^data:image\/(png|jpg);base64,/, "");
}
Getting a JPEG-formatted image doesn't work on older versions (around 3.5) of Firefox, so if you want to support that, you'll need to check the compatibility. If the encoding is not supported, it will default to "image/png".
This Function takes the URL then returns the image BASE64
function getBase64FromImageUrl(url) {
var img = new Image();
img.setAttribute('crossOrigin', 'anonymous');
img.onload = function () {
var canvas = document.createElement("canvas");
canvas.width =this.width;
canvas.height =this.height;
var ctx = canvas.getContext("2d");
ctx.drawImage(this, 0, 0);
var dataURL = canvas.toDataURL("image/png");
alert(dataURL.replace(/^data:image\/(png|jpg);base64,/, ""));
};
img.src = url;
}
Call it like this :
getBase64FromImageUrl("images/slbltxt.png")
Coming long after, but none of the answers here are entirely correct.
When drawn on a canvas, the passed image is uncompressed + all pre-multiplied.
When exported, its uncompressed or recompressed with a different algorithm, and un-multiplied.
All browsers and devices will have different rounding errors happening in this process
(see Canvas fingerprinting).
So if one wants a base64 version of an image file, they have to request it again (most of the time it will come from cache) but this time as a Blob.
Then you can use a FileReader to read it either as an ArrayBuffer, or as a dataURL.
function toDataURL(url, callback){
var xhr = new XMLHttpRequest();
xhr.open('get', url);
xhr.responseType = 'blob';
xhr.onload = function(){
var fr = new FileReader();
fr.onload = function(){
callback(this.result);
};
fr.readAsDataURL(xhr.response); // async call
};
xhr.send();
}
toDataURL(myImage.src, function(dataURL){
result.src = dataURL;
// now just to show that passing to a canvas doesn't hold the same results
var canvas = document.createElement('canvas');
canvas.width = myImage.naturalWidth;
canvas.height = myImage.naturalHeight;
canvas.getContext('2d').drawImage(myImage, 0,0);
console.log(canvas.toDataURL() === dataURL); // false - not same data
});
<img id="myImage" src="https://dl.dropboxusercontent.com/s/4e90e48s5vtmfbd/aaa.png" crossOrigin="anonymous">
<img id="result">
A more modern version of kaiido's answer using fetch would be:
function toObjectUrl(url) {
return fetch(url)
.then((response)=> {
return response.blob();
})
.then(blob=> {
return URL.createObjectURL(blob);
});
}
https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch
Edit: As pointed out in the comments this will return an object url which points to a file in your local system instead of an actual DataURL so depending on your use case this might not be what you need.
You can look at the following answer to use fetch and an actual dataURL: https://stackoverflow.com/a/50463054/599602
shiv / shim / sham
If your image(s) are already loaded (or not), this "tool" may come in handy:
Object.defineProperty
(
HTMLImageElement.prototype,'toDataURL',
{enumerable:false,configurable:false,writable:false,value:function(m,q)
{
let c=document.createElement('canvas');
c.width=this.naturalWidth; c.height=this.naturalHeight;
c.getContext('2d').drawImage(this,0,0); return c.toDataURL(m,q);
}}
);
.. but why?
This has the advantage of using the "already loaded" image data, so no extra request is needed. Additionally it lets the end-user (programmer like you) decide the CORS and/or mime-type and quality -OR- you can leave out these arguments/parameters as described in the MDN specification here.
If you have this JS loaded (prior to when it's needed), then converting to dataURL is as simple as:
examples
HTML
<img src="/yo.jpg" onload="console.log(this.toDataURL('image/jpeg'))">
JS
console.log(document.getElementById("someImgID").toDataURL());
GPU fingerprinting
If you are concerned about the "preciseness" of the bits then you can alter this tool to suit your needs as provided by #Kaiido's answer.
its 2022, I prefer to use modern createImageBitmap() instead of onload event.
*note: image should be same origin or CORS enabled
async function imageToDataURL(imageUrl) {
let img = await fetch(imageUrl);
img = await img.blob();
let bitmap = await createImageBitmap(img);
let canvas = document.createElement("canvas");
let ctx = canvas.getContext("2d");
canvas.width = bitmap.width;
canvas.height = bitmap.height;
ctx.drawImage(bitmap, 0, 0, bitmap.width, bitmap.height);
return canvas.toDataURL("image/png");
// image compression?
// return canvas.toDataURL("image/png", 0.9);
};
(async() => {
let dataUrl = await imageToDataURL('https://en.wikipedia.org/static/images/project-logos/enwiki.png')
wikiImg.src = dataUrl;
console.log(dataUrl)
})();
<img id="wikiImg">
Use onload event to convert image after loading
function loaded(img) {
let c = document.createElement('canvas')
c.getContext('2d').drawImage(img, 0, 0)
msg.innerText= c.toDataURL();
}
pre { word-wrap: break-word; width: 500px; white-space: pre-wrap; }
<img onload="loaded(this)" src="https://cors-anywhere.herokuapp.com/http://lorempixel.com/200/140" crossorigin="anonymous"/>
<pre id="msg"></pre>
This is all you need to read.
https://developer.mozilla.org/en-US/docs/Web/API/FileReader/readAsBinaryString
var height = 200;
var width = 200;
canvas.width = width;
canvas.height = height;
var ctx = canvas.getContext('2d');
ctx.strokeStyle = '#090';
ctx.beginPath();
ctx.arc(width/2, height/2, width/2 - width/10, 0, Math.PI*2);
ctx.stroke();
canvas.toBlob(function (blob) {
//consider blob is your file object
var reader = new FileReader();
reader.onload = function () {
console.log(reader.result);
}
reader.readAsBinaryString(blob);
});
In HTML5 better use this:
{
//...
canvas.width = img.naturalWidth; //img.width;
canvas.height = img.naturalHeight; //img.height;
//...
}
At the moment I get a byte array of a JPEG from my signalr connection and load it into a image and on the image.onload event I draw the image to my canvas.
so:
desktopImage.onload = function () {
myCanvasContext.drawImage(desktopImage, 0, 0);
}
chat.client.broadcastMessage = function (jpeg) {
desktopImage.src = 'data:image/jpeg;base64,' + jpeg;
}
Which does work well.
I was wondering whether i could 'quicken' this process by drawing the image directly onto the canvas without 1st loading it to an image 1st.
Is it possible?
If you receive a byte array as you state, assuming ArrayBuffer, you could wrap it as a Blob object and create an object URL for it instead. This will save significant overhead from encoding and decoding Base-64:
Example:
var desktopImage = new Image();
var url;
desktopImage.onload = function () {
myCanvasContext.drawImage(desktopImage, 0, 0);
(URL || webkitURL).revokeObjectURL(url); // release memory
}
// assuming jpeg = ArrayBuffer:
chat.client.broadcastMessage = function (jpeg) {
var blob = new Blob([jpeg], {type: "image/jpeg"});
url = (URL || webkitURL).createObjectURL(blob);
desktopImage.src = url;
}
Through a websocket, I retrieve a binary buffer of an image in a PNG format (something like that).
I want to load this PNG buffer into a canvas, and then read the different pixels to read the uncompressed data.
I managed to do it but it is stupid:
function onBinaryMessage(/*ArrayBuffer*/ input) {
input = new Uint8Array(input);
var str = '';
for (var i = 0; i < input.byteLength; i++)
str = str + String.fromCharCode(input[i]);
var image = document.getElementById("image");
image.src = 'data:image/png;base64,' + btoa(str);
var c = document.getElementById("canvas");
var ctx = c.getContext("2d");
ctx.drawImage(image, 0, 0);
var imgData = ctx.getImageData(0, 0, image.width, image.height);
console.log(imgData);
}
I have to convert my binary into a string to encode64 this string, and than I affect my image src to this newly created encoded64 string...
The browser have to re-convert this encoded64 data into the original PNG buffer I got...
Is there any way to directement set the canvas buffer?
Or is there any method to better handle streaming?
I think I could use the File API to write the buffer into a temporary file but it would cost a lot to create a file :(
Any suggestion?
You can convert your input buffer to a Blob instead, obtain an URL for it and use that to draw onto the canvas instead:
function onBinaryMessage(input) {
var blob = new Blob([input], {type: 'image/png'});
var url = URL.createObjectURL(blob);
var img = new Image;
img.onload = function() {
var ctx = document.getElementById("canvas").getContext('2d');
ctx.drawImage(this, 0, 0);
URL.revokeObjectURL(url);
}
img.src = url;
}
Just note that this will be asynchronous and you would need to provide a callback-mechanism inside the onload handler (in any case you really need to do that in your own example as well). But you won't have to convert to base64 etc. which is a relative costly operation.
Also note that URL.createObjectURL is currently experimental.
I'm making an plugin(add-on) to upload image on any page.
I only can get the url of the image, but I want to get the image data or local cache of image.
by javascript on chrome or firefox.
I did it in my extension via canvas.
I created two functions. First getting image data from canvas using "toDataURL()" method (Returns the content of the current canvas as an image that you can use as a source for another canvas or an HTML element (such as img)), and then using this data to get BLOB object.
function getImageDataURL(url) {
var data, canvas, ctx, blob;
var img = new Image();
img.onload = function() {
canvas = document.createElement("canvas");
canvas.width = img.width;
canvas.height = img.height;
ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0);
try {
data = canvas.toDataURL();
blob = dataURIToBlob(data);
} catch(e) {
// Handle errors here
alert(e);
}
};
img.src = url;
};
function dataURIToBlob (dataURI) {
var byteString = atob(dataURI.split(',')[1]);
var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];
var ab = [];
for (var i = 0; i < byteString.length; i++)
ab.push(byteString.charCodeAt(i));
return new Blob([new Uint8Array(ab)], { type: mimeString });
};
Here in the "blob" variable we have BLOB object with full image data.
You could use indexeDB (internal browser data base) that takes objets to store instead of URL. See Google dev tutorials for detailled use.