Obtenir l'URL des données d'image en JavaScript?

335

J'ai une page HTML régulière avec quelques images (juste des <img />balises HTML régulières ). J'aimerais obtenir leur contenu, encodé en base64 de préférence, sans avoir besoin de retélécharger l'image (c'est-à-dire qu'elle est déjà chargée par le navigateur, alors maintenant je veux le contenu).

J'adorerais y parvenir avec Greasemonkey et Firefox.

Detariael
la source

Réponses:

400

Remarque: cela ne fonctionne que si l'image provient du même domaine que la page ou a l' crossOrigin="anonymous"attribut et que le serveur prend en charge CORS. Cela ne vous donnera pas non plus le fichier d'origine, mais une version ré-encodée. Si vous avez besoin que le résultat soit identique à l'original, voir la réponse de Kaiido .


Vous devrez créer un élément de canevas avec les dimensions correctes et copier les données d'image avec la drawImagefonction. Ensuite, vous pouvez utiliser la toDataURLfonction pour obtenir une donnée: url qui a l'image encodée en base 64. Notez que l'image doit être entièrement chargée, sinon vous récupérerez simplement une image vide (noire, transparente).

Ce serait quelque chose comme ça. Je n'ai jamais écrit de script Greasemonkey, vous devrez donc peut-être ajuster le code pour qu'il s'exécute dans cet environnement.

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,/, "");
}

Obtenir une image au format JPEG ne fonctionne pas sur les anciennes versions (environ 3,5) de Firefox, donc si vous voulez prendre en charge cela, vous devrez vérifier la compatibilité. Si l'encodage n'est pas pris en charge, il sera par défaut "image / png".

Matthew Crumley
la source
1
Bien que cela semble fonctionner (sauf le caractère non échappé / dans le retour), cela ne crée pas la même chaîne base64 que celle que j'obtiens de PHP lorsque je fais base64_encode sur le fichier obtenu avec la fonction file_get_contents. Les images semblent très similaires / les mêmes, toujours celle en Javascript est plus petite et j'aimerais qu'elles soient exactement les mêmes. Encore une chose: l'image d'entrée est un petit (594 octets), 28x30 PNG avec fond transparent - si cela change quelque chose.
Detariael
7
Firefox pourrait utiliser un niveau de compression différent qui affecterait l'encodage. De plus, je pense que PNG prend en charge certaines informations d'en-tête supplémentaires comme les notes, qui seraient perdues, car la toile n'obtient que les données en pixels. Si vous avez besoin qu'il soit exactement le même, vous pouvez probablement utiliser AJAX pour obtenir le fichier et le coder en base64 manuellement.
Matthew Crumley
7
Oui, vous téléchargeriez l'image avec XMLHttpRequest. Avec un peu de chance, il utiliserait la version mise en cache de l'image, mais cela dépendrait de la configuration du serveur et du navigateur, et vous auriez la surcharge de demande pour déterminer si le fichier a changé. C'est pourquoi je n'ai pas suggéré cela en premier lieu :-) Malheureusement, pour autant que je sache, c'est le seul moyen d'obtenir le fichier d'origine.
Matthew Crumley
2
@trusktr Le drawImagene fera rien et vous vous retrouverez avec une toile vierge et l'image résultante.
Matthew Crumley
1
@JohnSewell C'est uniquement parce que l'OP voulait le contenu, pas une URL. Vous devez ignorer l'appel de remplacement (...) si vous souhaitez l'utiliser comme source d'image.
Matthew Crumley
76

Cette fonction prend l'URL puis retourne l'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;
}

Appelez ça comme ceci: getBase64FromImageUrl("images/slbltxt.png")

MuniR
la source
8
est-il possible de transformer une image d'un lien externe en base 64?
dinodsaurus
@dinodsaurus vous pouvez le charger dans une balise d'image ou faire une requête ajax.
Jared Forsyth
6
Eh bien, cela les oblige à implémenter CORS, mais alors vous faites juste $ .ajax (theimageurl) et cela devrait retourner quelque chose de raisonnable. Sinon (si CORS n'est pas activé), cela ne fonctionnera pas; le modèle de sécurité d'Internet l'interdit. À moins, bien sûr, que vous ne soyez à l'intérieur d'un plugin Chrome, auquel cas tout est autorisé - même l'exemple ci-dessus
Jared Forsyth
4
Vous devez mettre img.src =après img.onload =, car dans certains navigateurs, comme Opera, l'événement ne se produira pas.
ostapische
1
@Hakkar, une fuite de mémoire ne se produira que dans les anciens navigateurs qui utilisent toujours le nombre de références pour informer la collecte des ordures. À ma connaissance, la référence circulaire ne crée pas de fuite dans une configuration de marquage et de balayage . Une fois que l'étendue des fonctions getBase64FromImageUrl(url)et img.onload = function ()leur sortie sont img est inaccessible et les ordures collectées. Autre que IE 6/7 et c'est ok.
humanANDpeace
59

Venant longtemps après, mais aucune des réponses ici n'est entièrement correcte.

Lorsqu'elle est dessinée sur un canevas, l'image transmise est non compressée + toutes pré-multipliées.
Lorsqu'il est exporté, il est non compressé ou recompressé avec un algorithme différent et non multiplié.

Tous les navigateurs et les appareils auront des erreurs d'arrondi différentes se produisant dans ce processus
(voir Empreinte digitale de Canvas ).

Donc, si l'on veut une version base64 d'un fichier image, il faut la demander à nouveau (la plupart du temps elle proviendra du cache) mais cette fois en Blob.

Ensuite, vous pouvez utiliser un FileReader pour le lire soit en tant que ArrayBuffer, soit en tant que 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">

Kaiido
la source
Je viens de passer en revue plus de 5 questions différentes et votre code est le seul qui fonctionne correctement, merci :)
Peter
1
J'obtiens cette erreur avec le code ci-dessus. XMLHttpRequest cannot load data:image/jpeg;base64,/9j/4AA ... /2Q==. Invalid response. Origin 'https://example.com' is therefore not allowed access.
STWilson
2
@STWilson, oui, cette méthode est également liée à la même politique d'origine, tout comme celle du canevas. En cas de demande d'origine croisée, vous devez configurer le serveur d'hébergement pour autoriser de telles demandes d'origine croisée à votre script.
Kaiido
@Kaiido donc si l'image se trouve sur un autre serveur que le script le serveur d'hébergement doit activer les requêtes d'origine croisée? Si vous définissez img.setAttribute («crossOrigin», «anonyme»), est-ce que cela empêche l'erreur?
1,21 gigawatts
1
Après plus de recherches, il semble que les images puissent utiliser l'attribut crossOrigin pour demander l'accès, mais c'est au serveur d'hébergement de donner accès via un en-tête CORS , sitepoint.com/an-in-depth-look-at-cors . Quant à XMLHttpRequest, il semble que le serveur doive donner l'accès de la même manière que pour les images via un en-tête CORS mais aucune modification n'est requise dans votre code, sauf peut-être en définissant xhr.withCredentials sur false (par défaut).
1,21 gigawatts
20

Une version plus moderne de la réponse de kaiido utilisant fetch serait:

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: Comme indiqué dans les commentaires, cela renverra une URL d'objet qui pointe vers un fichier dans votre système local au lieu d'une DataURL réelle, donc selon votre cas d'utilisation, cela pourrait ne pas être ce dont vous avez besoin.

Vous pouvez consulter la réponse suivante pour utiliser l'extraction et une URL de données réelle: https://stackoverflow.com/a/50463054/599602

Zaptree
la source
2
N'oubliez pas d'appeler URL.revokeObjectURL()si votre application utilise cette méthode depuis longtemps, sinon votre mémoire sera encombrée.
Ingénieur
1
Attention: DataURL (encodé en base64) n'est pas le même que ObjectURL (référence au blob)
DaniEll
1
ObjectURL n'est certainement pas une URL de données. Ce n'est pas une bonne réponse.
Danny Lin
2

shiv / shim / sham

Si vos images sont déjà chargées (ou pas), cet "outil" peut être utile:

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

.. mais pourquoi?

Cela a l'avantage d'utiliser les données d'image "déjà chargées", donc aucune demande supplémentaire n'est nécessaire. De plus, cela permet à l'utilisateur final (programmeur comme vous) de décider du CORS et / ou mime-typeet quality-OU- vous pouvez omettre ces arguments / paramètres comme décrit dans la spécification MDN ici .

Si vous avez chargé ce JS (avant qu'il ne soit nécessaire), la conversion en dataURLest aussi simple que:

exemples

HTML
<img src="/yo.jpg" onload="console.log(this.toDataURL('image/jpeg'))">
JS
console.log(document.getElementById("someImgID").toDataURL());

Empreinte digitale du GPU

Si vous êtes préoccupé par la «précision» des bits, vous pouvez modifier cet outil en fonction de vos besoins, comme indiqué dans la réponse de @ Kaiido.

argon
la source
1

Utilisez l' onloadévénement pour convertir l'image après le chargement

Kamil Kiełczewski
la source
-3

En HTML5 mieux utiliser ceci:

{
//...
canvas.width = img.naturalWidth; //img.width;
canvas.height = img.naturalHeight; //img.height;
//...
}
KepHec
la source
2
En répondant à la question, essayez d'être plus précis et fournissez des explications détaillées.
Piyush Zalani