Get image data URL in JavaScript?
Asked Answered
P

9

378

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.

Petrapetracca answered 1/6, 2009 at 8:43 Comment(0)
G
432

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".

Gellman answered 1/6, 2009 at 13:55 Comment(29)
While this seems to be working (except the unescaped / in the return), it does not create the same base64 string as the one I'm getting from PHP when doing base64_encode on the file obtained with file_get_contents function. The images seem very similar/the same, still the Javascripted one is smaller and I'd love them to be exactly the same. One more thing: the input image is a small (594 bytes), 28x30 PNG with transparent background -- if that changes anything.Petrapetracca
Also motobit.com/util/base64-decoder-encoder.asp confirms that the Javascript's Base64 encoded string is wrong - if created from the original file, the result is the same as mine from PHP and different from the one from Javascript.Petrapetracca
Firefox could be using a different compression level which would affect the encoding. Also, I think PNG supports some extra header information like notes, that would be lost, since the canvas only gets the pixel data. If you need it to be exactly the same, you could probably use AJAX to get the file and base64 encode it manually.Gellman
But what exactly from AJAX do I need? XMLHttpRequest? But isn't it the exact thing I'm trying to aviod - redownloading the file?Petrapetracca
Yes, you would download the image with XMLHttpRequest. Hopefully, it would use the cached version of the image, but that would depend on the server and browser configuration, and you would have the request overhead to determine if the file has changed. That's why I didn't suggest that in the first place :-) Unfortunately, as far as I know, it's the only way to get the original file.Gellman
And if I decided to go with XMLHttpRequest (or GM's version of it), what would I do with the result? How to convert it to base64 form? Canvas aren't an answer because of the problems above I think. Or maybe there is a way to save a file to the hard drive with JavaScript somehow (but I'm afraid that's not possible due to security reasons)? Will converting my GM script to Fx extension or writing a Fx extension from the scratch give me more power/access to cache files in some way?Petrapetracca
You would need to use a base64 encoder written in javascript. If found this: webtoolkit.info/javascript-base64.html by searching google. I've never used it, but I have used some other code from webtoolkit.info. For the second part, I don't know what kind of permissions GM gives you, but I know extensions can read and write files.Gellman
But I still miss the part from getting the image with XMLHttpRequest to passing image to some Base64 function. How would I do that, still use the Canvas that has proven unreliable?Petrapetracca
I think you could pass the xhr.reponseText property to the base64 encoder when the request is done.Gellman
I've decided to ditch it for just passing the URL to the PHP script, which would run through caching proxy that the browser will also use. Since your code was doing the thing pretty well, I'm marking it as an answer, even if it's not the ultimate solution.Petrapetracca
Question: If you try to do this before the images are fully loaded, what will happen?Urea
@Urea The drawImage will do nothing, and you'll end up with a blank canvas and resulting image.Gellman
I'm getting this error any Idea? TypeError: Value could not be converted to any of: HTMLImageElement, HTMLCanvasElement, HTMLVideoElementKenakenaf
@SachinPrasad웃 You might want to make that a new question, but it looks like whatever element you're passing to drawImage is not an image.Gellman
@MatthewCrumley i have this code but when i m applying this code in loop than it is returning Base64 after whole loop executes.I want for each image it will return base64 at the same time.. how it will be possible?Overactive
@SaurabhAndroid It sounds like you would want want to put the results in an array and return that.Gellman
@MatthewCrumley yes exactly, How it will possible?Overactive
image/jpeg is also valid!Viquelia
Hi, I plan to use this in phonegap+angularjs, my question is, do I have to delete the created element document.createElement("canvas")? I will be encoding list of <img> so I'm worried with the amount of memory that it would take if I create list of canvas element. Please answer. Thanks!Guitarfish
@Guitarfish No, as long as you don't keep any references to the canvas around, the garbage collector should take care of it for you.Gellman
Mmm.. the answer doesn't seem to work with GIF files.Hernia
Here's another approach which coud be done without AngularJS; I guess it's a non-lossy method.Hiett
why don't use dataURL.replace(/^data:image\/([a-z]*);base64,/, "")Radmilla
@sinu I probably didn't have any particular reason, since that works too.Gellman
Why do you need to replace data:image/png please? It does not get work if past to img.srcConstrict
@JohnSewell That's only because the OP wanted the content, not a URL. You would need to skip the replace(...) call if you want to use it as an image source.Gellman
This works only if your image source is on the same domain (or you hava valid CORS headers)Humphreys
@SamJasonBraddock That's a good point. I'm not sure why I didn't mention that, but I'll add a note.Gellman
using this.width and this.height will give you the image size as fitted in the rendered item but if the image itself is larger it will result in only part of the image being rendered on the canvas. To fix this you can use this.naturalHeight and this.naturalWitdth insteadDiaphane
D
90

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">
Deferential answered 21/3, 2017 at 1:16 Comment(12)
Just gone through like 5+ different questions and your code is the only one that's worked correctly, thanks :)Flex
I get this error with the above code. XMLHttpRequest cannot load data:image/jpeg;base64,/9j/4AA ... /2Q==. Invalid response. Origin 'https://example.com' is therefore not allowed access.Glazed
@STWilson, yes, this method is also tied to the same-origin policy, just like the canvas one. In case of cross-origin request, you need to configure the hosting server to allow such cross-origin requests to your script.Deferential
@Deferential so if the image is on another server than the script the hosting server has to enable cross origin requests? If you set img.setAttribute('crossOrigin', 'anonymous') does that prevent the error?Arran
Upon more research, it looks like image's can use the crossOrigin attribute to request access but it's up to the hosting server to give access through a CORS header, sitepoint.com/an-in-depth-look-at-cors. As for XMLHttpRequest's it looks like the server has to give access the same as for images through a CORS header but no change is required in your code except maybe setting xhr.withCredentials to false (the default).Arran
Copied your code verbatim and I get this error Uncaught ReferenceError: result is not definedOffering
@Offering result is the id of the second image element. Similar to document.getElementById('result') but in a not really well written syntax.Deferential
thanks again for another awesome answer. what if you don't need a base64 version but just need the dataURI for SVG serialization? do you still need to reload the image even if it's already loaded in the page?Barajas
@Barajas then you need the base64 version, and no, you don't technically need to reload the image, only to fetch it again (all the decoding part of to load an image is unnecessary here), and this fetch will most of the time just be grab from cache, so no new network request either.Deferential
to clarify for future readers, you're advising people to use filereader, not canvas, to fetch data URLs because canvas may alter the image whereas filereader does not. correct?Barajas
@Deferential Is there any critical reason why the two data urls are different, or just that they use different algorithm? I can see that the canvas version is about half the size of the other.Toogood
Oh from exif.regex.info/exif.cgi I can see that there're many other information stored in the original version. Many of them was stored by the editor software like Photoshop.Toogood
J
88

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

Janejanean answered 15/5, 2013 at 13:21 Comment(12)
is there a way to turn an image from an external link to base 64 ?Reginaldreginauld
@Reginaldreginauld you could load it in an image tag, or do an ajax query.Beak
I'm getting an incomplete image doing this. Do you have any idea why?Amboceptor
@JaredForsyth may i know what do you mean by "do an ajax query" ?Machination
Well, it requires them to implement CORS, but then you just do $.ajax(theimageurl) and it should return something reasonable. Otherwise (if they don't have CORS enabled) it won't work; the security model of the internet disallows it. Unless, of course, you're inside of a chrome plugin, in which case everything is allowed - even the above exampleBeak
You must put img.src = after img.onload =, because in some browsers, such as Opera, the event will not happen.Brio
@MuniR i have this code but when i m applying this code in loop than it is returning Base64 after whole loop executes.I want for each image it will return base64 at the same time.. how it will be possible?Overactive
Saurabh Android . if you meant after finishing the loop you will get just one image , this because each time the function replaces the URL , and so the last URL remains, to show all images you have to Open them in new tab instead of (.replace)Janejanean
This will result in a memory leak, because the img 'onload' event is never unbound.Rafat
@Rafat a memory leak will only occur in old browsers that use still reference count to inform garbage collection. To my comprehension the circular reference does not create leak in a mark and sweep setup. Once the functions' scopes of getBase64FromImageUrl(url) and img.onload = function () are exited img is unreachable and garbage collected. Other than IE 6/7 and this is ok.Selfrestraint
thanks for this. However, I need to have multiple images now. so I pass the array of image url and I loop one by one to generate data url. The problem is when it creates a canvas for the first image then the second create canvas, too and it replace the first image, and it becomes a mess. I moved to use setInterval to give some short time to create canvas and generate data url. but not work well in production. Any solutions for that?Ariana
I am getting error on Image() is not defined ! Where it is coming from ?Botts
C
32

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://mcmap.net/q/17262/-requesting-blob-images-and-transforming-to-base64-with-fetch-api

Chevet answered 21/7, 2017 at 21:36 Comment(3)
Don't forget to call URL.revokeObjectURL() if you have a long running application using this method, or your memory will get cluttered.Dishevel
Attention: DataURL (base64 encoded) is not the same as ObjectURL (reference to blob)Assyrian
ObjectURL is certainly not data URL. This is not a correct answer.Disruptive
J
9

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.

Julide answered 1/11, 2019 at 1:23 Comment(0)
H
7

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">
Heartache answered 6/7, 2022 at 0:48 Comment(0)
D
1

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>
Deliver answered 24/4, 2019 at 8:3 Comment(0)
M
0

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);
});
Mosely answered 16/7, 2020 at 18:1 Comment(0)
G
-1

In HTML5 better use this:

{
//...
canvas.width = img.naturalWidth; //img.width;
canvas.height = img.naturalHeight; //img.height;
//...
}
Gide answered 12/1, 2019 at 10:18 Comment(2)
While answering the question try to be more specific and provide detailed explanations.Casual
Thanks for pointing out this! I am always getting the cropped image, without knowing this.Kauai

© 2022 - 2024 — McMap. All rights reserved.