Obtener URL de datos de imagen en JavaScript?

335

Tengo una página HTML normal con algunas imágenes (solo <img />etiquetas HTML normales ). Me gustaría obtener su contenido, codificado en base64 preferiblemente, sin la necesidad de volver a descargar la imagen (es decir, ya está cargada por el navegador, así que ahora quiero el contenido).

Me encantaría lograr eso con Greasemonkey y Firefox.

Detariael
fuente

Respuestas:

400

Nota: Esto solo funciona si la imagen es del mismo dominio que la página, o tiene el crossOrigin="anonymous"atributo y el servidor admite CORS. Tampoco le dará el archivo original, sino una versión codificada nuevamente. Si necesita que el resultado sea idéntico al original, vea la respuesta de Kaiido .


Deberá crear un elemento de lienzo con las dimensiones correctas y copiar los datos de la imagen con la drawImagefunción. Luego puede usar la toDataURLfunción para obtener datos: url que tiene la imagen codificada en base 64. Tenga en cuenta que la imagen debe estar completamente cargada, o simplemente obtendrá una imagen vacía (negra, transparente).

Sería algo como esto. Nunca he escrito un script de Greasemonkey, por lo que es posible que deba ajustar el código para que se ejecute en ese entorno.

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

Obtener una imagen con formato JPEG no funciona en versiones anteriores (alrededor de 3.5) de Firefox, por lo que si desea admitir eso, deberá verificar la compatibilidad. Si la codificación no es compatible, el valor predeterminado será "image / png".

Matthew Crumley
fuente
1
Si bien esto parece estar funcionando (excepto el no escapado / en el retorno), no crea la misma cadena base64 que la que obtengo de PHP cuando hago base64_encode en el archivo obtenido con la función file_get_contents. Las imágenes parecen muy similares / iguales, aún así la de Javascripted es más pequeña y me encantaría que fueran exactamente iguales. Una cosa más: la imagen de entrada es un pequeño (594 bytes), 28x30 PNG con fondo transparente, si eso cambia algo.
Detariael 01 de
77
Firefox podría estar usando un nivel de compresión diferente que afectaría la codificación. Además, creo que PNG admite información adicional de encabezado como notas, que se perderían, ya que el lienzo solo obtiene los datos de píxeles. Si necesita que sea exactamente igual, probablemente podría usar AJAX para obtener el archivo y codificarlo en base64 manualmente.
Matthew Crumley
77
Sí, descargaría la imagen con XMLHttpRequest. Con suerte, usaría la versión en caché de la imagen, pero eso dependería de la configuración del servidor y del navegador, y tendría la sobrecarga de la solicitud para determinar si el archivo ha cambiado. Es por eso que no sugerí eso en primer lugar :-) Desafortunadamente, hasta donde yo sé, es la única forma de obtener el archivo original.
Matthew Crumley
2
@trusktr No drawImagehará nada, y terminarás con un lienzo en blanco y la imagen resultante.
Matthew Crumley
1
@JohnSewell Eso es solo porque el OP quería el contenido, no una URL. Debería omitir la llamada de reemplazo (...) si desea usarla como fuente de imagen.
Matthew Crumley,
76

Esta función toma la URL y luego devuelve la imagen 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;
}

Llámalo así: getBase64FromImageUrl("images/slbltxt.png")

MuniR
fuente
8
¿Hay alguna manera de convertir una imagen desde un enlace externo a la base 64?
Dinodsaurus
@dinodsaurus, puede cargarlo en una etiqueta de imagen o hacer una consulta ajax.
Jared Forsyth
66
Bueno, requiere que implementen CORS, pero luego solo debe hacer $ .ajax (theimageurl) y debería devolver algo razonable. De lo contrario (si no tienen CORS habilitado) no funcionará; el modelo de seguridad de internet no lo permite. A menos, por supuesto, que esté dentro de un complemento de Chrome, en cuyo caso todo está permitido, incluso el ejemplo anterior
Jared Forsyth el
44
Debe poner img.src =después img.onload =, porque en algunos navegadores, como Opera, el evento no sucederá.
ostapische
1
@Hakkar, solo se producirá una pérdida de memoria en los navegadores antiguos que todavía usan el recuento de referencias para informar la recolección de basura. A mi entender, la referencia circular no crea fugas en una configuración de marca y barrido . Una vez que los ámbitos de las funciones de getBase64FromImageUrl(url)y img.onload = function ()se han salido, img es inalcanzable y se recolecta basura. Aparte de IE 6/7 y esto está bien.
humanityANDpeace
59

Mucho después, pero ninguna de las respuestas aquí es completamente correcta.

Cuando se dibuja en un lienzo, la imagen pasada se descomprime y se multiplica previamente.
Cuando se exporta, se descomprime o vuelve a comprimir con un algoritmo diferente y no se multiplica.

Todos los navegadores y dispositivos tendrán diferentes errores de redondeo en este proceso
(ver Huellas digitales de Canvas ).

Entonces, si uno quiere una versión base64 de un archivo de imagen, debe solicitarlo nuevamente (la mayoría de las veces vendrá del caché) pero esta vez como un Blob.

Luego puede usar un FileReader para leerlo como ArrayBuffer o como 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
fuente
Acabo de leer más de 5 preguntas diferentes y tu código es el único que funcionó correctamente, gracias :)
Peter
1
Me sale este error con el código anterior. XMLHttpRequest cannot load data:image/jpeg;base64,/9j/4AA ... /2Q==. Invalid response. Origin 'https://example.com' is therefore not allowed access.
STWilson
2
@STWilson, sí, este método también está vinculado a la política del mismo origen, al igual que el lienzo. En caso de solicitud de origen cruzado, debe configurar el servidor de alojamiento para permitir tales solicitudes de origen cruzado en su script.
Kaiido
@Kaiido, así que si la imagen está en otro servidor que el script, ¿el servidor de alojamiento debe habilitar las solicitudes de origen cruzado? Si configura img.setAttribute ('crossOrigin', 'anonymous'), ¿eso evita el error?
1.21 gigavatios
1
Después de más investigación, parece que las imágenes pueden usar el atributo crossOrigin para solicitar acceso, pero depende del servidor de alojamiento dar acceso a través de un encabezado CORS, sitepoint.com/an-in-depth-look-at-cors . En cuanto a XMLHttpRequest, parece que el servidor tiene que dar acceso al mismo que para las imágenes a través de un encabezado CORS, pero no se requiere ningún cambio en su código, excepto tal vez establecer xhr.withCredentials en falso (el valor predeterminado).
1.21 gigavatios
20

Una versión más moderna de la respuesta de kaiido usando fetch sería:

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

Editar: como se señaló en los comentarios, esto devolverá una URL de objeto que apunta a un archivo en su sistema local en lugar de un DataURL real, por lo que, según su caso de uso, esto podría no ser lo que necesita.

Puede consultar la siguiente respuesta para usar fetch y una dataURL real: https://stackoverflow.com/a/50463054/599602

Zaptree
fuente
2
No olvides llamar URL.revokeObjectURL()si tienes una aplicación de larga ejecución que usa este método, o tu memoria se desordenará.
Ingeniero
1
Atención: DataURL (codificado en base64) no es lo mismo que ObjectURL (referencia a blob)
DaniEll
1
ObjectURL ciertamente no es una URL de datos. Esta no es una respuesta correcta.
Danny Lin
2

shiv / shim / sham

Si sus imágenes ya están cargadas (o no), esta "herramienta" puede ser útil:

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

.. ¿pero por qué?

Esto tiene la ventaja de utilizar los datos de imagen "ya cargados", por lo que no se necesita ninguna solicitud adicional. Adicionalmente permite que el usuario final (programador como usted) decidir las CORS y / o mime-typee quality-O- se puede dejar de lado estos argumentos / parámetros tal como se describe en el MDN especificación aquí .

Si tiene este JS cargado (antes de cuando sea necesario), la conversión a dataURLes tan simple como:

ejemplos

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

Huella digital GPU

Si le preocupa la "precisión" de los bits, puede modificar esta herramienta para que se ajuste a sus necesidades según lo dispuesto por la respuesta de @ Kaiido.

argón
fuente
1

Use el onloadevento para convertir la imagen después de cargar

Kamil Kiełczewski
fuente
-3

En HTML5 mejor use esto:

{
//...
canvas.width = img.naturalWidth; //img.width;
canvas.height = img.naturalHeight; //img.height;
//...
}
KepHec
fuente
2
Mientras contesta la pregunta, trate de ser más específico y brinde explicaciones detalladas.
Piyush Zalani