376

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.

Syscall
  • 19,327
  • 10
  • 37
  • 52
Detariael
  • 4,152
  • 4
  • 19
  • 10

9 Answers9

429

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

Community
  • 1
  • 1
Matthew Crumley
  • 101,441
  • 24
  • 103
  • 129
  • 1
    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. – Detariael Jun 01 '09 at 14:22
  • Also http://www.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. – Detariael Jun 01 '09 at 14:32
  • 7
    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. – Matthew Crumley Jun 01 '09 at 15:13
  • But what exactly from AJAX do I need? XMLHttpRequest? But isn't it the exact thing I'm trying to aviod - redownloading the file? – Detariael Jun 01 '09 at 16:23
  • 7
    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. – Matthew Crumley Jun 01 '09 at 16:44
  • 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? – Detariael Jun 01 '09 at 16:55
  • You would need to use a base64 encoder written in javascript. If found this: http://www.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. – Matthew Crumley Jun 01 '09 at 18:18
  • 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? – Detariael Jun 01 '09 at 20:04
  • I think you could pass the xhr.reponseText property to the base64 encoder when the request is done. – Matthew Crumley Jun 01 '09 at 22:07
  • 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. – Detariael Jun 02 '09 at 18:46
  • Question: If you try to do this before the images are fully loaded, what will happen? – trusktr Jun 14 '12 at 03:50
  • 3
    @trusktr The `drawImage` will do nothing, and you'll end up with a blank canvas and resulting image. – Matthew Crumley Jun 14 '12 at 14:00
  • I'm getting this error any Idea? TypeError: Value could not be converted to any of: HTMLImageElement, HTMLCanvasElement, HTMLVideoElement – Sachin Prasad Apr 25 '13 at 13:31
  • @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. – Matthew Crumley Apr 25 '13 at 13:46
  • @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? – Saurabh Android May 01 '14 at 10:34
  • @SaurabhAndroid It sounds like you would want want to put the results in an array and return that. – Matthew Crumley May 01 '14 at 13:41
  • @MatthewCrumley yes exactly, How it will possible? – Saurabh Android May 01 '14 at 14:06
  • `image/jpeg` is also valid! – Meekohi May 20 '14 at 23:33
  • 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 `` so I'm worried with the amount of memory that it would take if I create list of `canvas` element. Please answer. Thanks! – fiberOptics Sep 25 '14 at 02:02
  • @fiberOptics No, as long as you don't keep any references to the canvas around, the garbage collector should take care of it for you. – Matthew Crumley Sep 25 '14 at 16:57
  • Mmm.. the answer doesn't seem to work with GIF files. – Robert Dec 22 '14 at 17:05
  • Here's [another approach](http://stackoverflow.com/questions/16775729/angular-js-request-in-order-to-get-an-image/29578167#29578167) which coud be done without AngularJS; I guess it's a non-lossy method. – Daan Apr 11 '15 at 14:27
  • why don't use `dataURL.replace(/^data:image\/([a-z]*);base64,/, "")` – sinujohn Apr 29 '15 at 10:54
  • @sinu I probably didn't have any particular reason, since that works too. – Matthew Crumley Apr 30 '15 at 17:34
  • Why do you need to replace data:image/png please? It does not get work if past to img.src – John Sewell May 08 '16 at 23:31
  • 1
    @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. – Matthew Crumley May 09 '16 at 14:36
  • 1
    This works only if your image source is on the same domain (or you hava valid CORS headers) – Sam Jason Braddock Apr 20 '17 at 15:40
  • @SamJasonBraddock That's a good point. I'm not sure why I didn't mention that, but I'll add a note. – Matthew Crumley Apr 20 '17 at 18:20
  • 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` instead – anyab Jun 26 '17 at 10:57
86

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

Ionică Bizău
  • 109,027
  • 88
  • 289
  • 474
MoniR
  • 1,351
  • 12
  • 18
  • 9
    is there a way to turn an image from an external link to base 64 ? – dinodsaurus May 29 '13 at 09:10
  • @dinodsaurus you could load it in an image tag, or do an ajax query. – Jared Forsyth Jul 29 '13 at 20:31
  • I'm getting an incomplete image doing this. Do you have any idea why? – emen Sep 20 '13 at 02:19
  • @JaredForsyth may i know what do you mean by "do an ajax query" ? – abc cba Nov 16 '13 at 17:08
  • 7
    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 example – Jared Forsyth Nov 16 '13 at 20:16
  • 4
    You must put `img.src =` after `img.onload =`, because in some browsers, such as Opera, the event will not happen. – ostapische Dec 26 '13 at 05:31
  • @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? – Saurabh Android May 01 '14 at 10:26
  • 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) – MoniR Sep 03 '14 at 10:02
  • This will result in a memory leak, because the img 'onload' event is never unbound. – Hakkar Feb 16 '15 at 19:26
  • 2
    @Hakkar a memory leak will only occur in old browsers that use still [reference count](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Memory_Management#Reference-counting_garbage_collection) to inform garbage collection. To my comprehension the circular reference does not create leak in a [mark and sweep](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Memory_Management#Mark-and-sweep_algorithm) 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. – humanityANDpeace Jan 31 '16 at 13:35
  • 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? – chourn solidet May 05 '17 at 02:04
  • I am getting error on `Image()` is not defined ! Where it is coming from ? – Ali Yar Khan Feb 07 '22 at 07:22
85

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">
lorefnon
  • 12,875
  • 6
  • 61
  • 93
Kaiido
  • 123,334
  • 13
  • 219
  • 285
  • 2
    Just gone through like 5+ different questions and your code is the only one that's worked correctly, thanks :) – Peter Mar 26 '17 at 15:06
  • 1
    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.` – STWilson May 02 '17 at 20:53
  • 3
    @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. – Kaiido May 03 '17 at 02:10
  • @Kaiido 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? – 1.21 gigawatts May 14 '17 at 19:46
  • 1
    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, https://www.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). – 1.21 gigawatts May 14 '17 at 21:21
  • Copied your code verbatim and I get this error `Uncaught ReferenceError: result is not defined` – jkupczak Jun 30 '17 at 22:09
  • @jkupczak `result` is the `id` of the second image element. Similar to `document.getElementById('result')` but in a not really well written syntax. – Kaiido Jul 02 '17 at 02:55
  • 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? – Crashalot Dec 31 '18 at 21:40
  • @Crashalot 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. – Kaiido Jan 01 '19 at 02:26
  • 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? – Crashalot Jul 31 '20 at 08:22
  • @Kaiido 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. – Uahnbu Tran Aug 31 '21 at 18:50
  • Oh from http://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. – Uahnbu Tran Aug 31 '21 at 19:01
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://stackoverflow.com/a/50463054/599602

Zaptree
  • 3,763
  • 1
  • 31
  • 26
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.

argon
  • 449
  • 4
  • 11
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">
uingtea
  • 6,002
  • 2
  • 26
  • 40
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>
Kamil Kiełczewski
  • 85,173
  • 29
  • 368
  • 345
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);
});
Ash Singh
  • 3,921
  • 2
  • 25
  • 30
-1

In HTML5 better use this:

{
//...
canvas.width = img.naturalWidth; //img.width;
canvas.height = img.naturalHeight; //img.height;
//...
}
KepHec
  • 27
  • 1