ChatGPT解决这个技术问题 Extra ChatGPT

Get image data URL in JavaScript?

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.


C
Community

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


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.
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.
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.
@trusktr The drawImage will do nothing, and you'll end up with a blank canvas and resulting image.
@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.
I
Ionică Bizău

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


is there a way to turn an image from an external link to base 64 ?
@dinodsaurus you could load it in an image tag, or do an ajax query.
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
You must put img.src = after img.onload =, because in some browsers, such as Opera, the event will not happen.
@Hakkar 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.
l
lorefnon

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 });


Just gone through like 5+ different questions and your code is the only one that's worked correctly, thanks :)
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, 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 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?
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).
Z
Zaptree

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


Don't forget to call URL.revokeObjectURL() if you have a long running application using this method, or your memory will get cluttered.
Attention: DataURL (base64 encoded) is not the same as ObjectURL (reference to blob)
ObjectURL is certainly not data URL. This is not a correct answer.
a
argon

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 in needed. Aditionally 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.


Cool!!! So useful. This should be voted to the top.
Excellent approach! Well put.
K
Kamil Kiełczewski

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; }


                			                   			


u
uingtea

its 2022, why still use onload event, use createImageBitmap() instead.

*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) })();


A
Ash Singh

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);
});

K
KepHec

In HTML5 better use this:

{
//...
canvas.width = img.naturalWidth; //img.width;
canvas.height = img.naturalHeight; //img.height;
//...
}

While answering the question try to be more specific and provide detailed explanations.
Thanks for pointing out this! I am always getting the cropped image, without knowing this.