Obter URL de dados de imagem em JavaScript?

335

Eu tenho uma página HTML regular com algumas imagens (apenas <img />tags HTML regulares ). Eu gostaria de obter o conteúdo deles, codificado com base64 de preferência, sem a necessidade de baixar novamente a imagem (ou seja, ela já foi carregada pelo navegador, agora eu quero o conteúdo).

Eu adoraria conseguir isso com o Greasemonkey e o Firefox.

Detariael
fonte

Respostas:

400

Nota: Isso funciona apenas se a imagem for do mesmo domínio da página ou tiver o crossOrigin="anonymous"atributo e o servidor suportar CORS. Também não fornecerá o arquivo original, mas uma versão recodificada. Se você precisar que o resultado seja idêntico ao original, consulte a resposta de Kaiido .


Você precisará criar um elemento de tela com as dimensões corretas e copiar os dados da imagem com a drawImagefunção Em seguida, você pode usar a toDataURLfunção para obter um dado: url que tenha a imagem codificada na base 64. Observe que a imagem deve estar totalmente carregada ou você receberá uma imagem vazia (preta, transparente).

Seria algo assim. Eu nunca escrevi um script Greasemonkey, portanto, pode ser necessário ajustar o código para ser executado nesse ambiente.

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

A obtenção de uma imagem formatada em JPEG não funciona em versões mais antigas (cerca de 3,5) do Firefox, por isso, se você quiser dar suporte a isso, precisará verificar a compatibilidade. Se a codificação não for suportada, o padrão será "image / png".

Matthew Crumley
fonte
11
Embora isso pareça estar funcionando (exceto o sem escape / no retorno), ele não cria a mesma string base64 que a que estou recebendo do PHP ao executar base64_encode no arquivo obtido com a função file_get_contents. As imagens parecem muito semelhantes / iguais, mas a Javascripted é menor e eu adoraria que elas fossem exatamente iguais. Mais uma coisa: a imagem de entrada é uma pequena (594 bytes), 28x30 PNG com fundo transparente - se isso mudar alguma coisa.
Detariael 01/06/2009
7
O Firefox pode estar usando um nível de compactação diferente, o que afetaria a codificação. Além disso, acho que o PNG suporta algumas informações adicionais do cabeçalho, como notas, que seriam perdidas, uma vez que a tela obtém apenas os dados de pixel. Se você precisar que seja exatamente o mesmo, provavelmente poderá usar o AJAX para obter o arquivo e a base64 codificá-lo manualmente.
Matthew Crumley
7
Sim, você faria o download da imagem com XMLHttpRequest. Felizmente, ele usaria a versão em cache da imagem, mas isso dependeria da configuração do servidor e do navegador, e você teria a sobrecarga da solicitação para determinar se o arquivo foi alterado. É por isso que eu não sugeri que, em primeiro lugar :-) Infelizmente, até onde eu sei, é a única maneira de obter o arquivo original.
Matthew Crumley
2
@trusktr O drawImagenão fará nada, e você terminará com uma tela em branco e a imagem resultante.
Matthew Crumley
11
@JohnSewell Isso é apenas porque o OP queria o conteúdo, não um URL. Você precisaria pular a chamada de substituição (...) se quiser usá-la como fonte de imagem.
Matthew Crumley 9/16
76

Esta função pega o URL e retorna a imagem 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;
}

Chame assim: getBase64FromImageUrl("images/slbltxt.png")

MuniR
fonte
8
existe uma maneira de transformar uma imagem de um link externo para a base 64?
dinodsaurus 29/05
@dinodsaurus você pode carregá-lo em uma tag de imagem ou fazer uma consulta ajax.
Jared Forsyth
6
Bem, ele exige que eles implementem o CORS, mas então você faz apenas $ .ajax (theimageurl) e deve retornar algo razoável. Caso contrário (se eles não tiverem o CORS ativado), não funcionará; o modelo de segurança da internet não o permite. A menos, claro, você está dentro de um plugin cromo, caso em que tudo é permitido - até mesmo o exemplo acima
Jared Forsyth
4
Você deve colocar img.src =depois img.onload =, porque em alguns navegadores, como o Opera, o evento não acontecerá.
ostapische
11
@Hakkar: um vazamento de memória ocorrerá apenas em navegadores antigos que ainda usam contagem de referência para informar a coleta de lixo. Para minha compreensão, a referência circular não cria vazamento em uma configuração de marcação e varredura . Uma vez que os escopos das funções getBase64FromImageUrl(url)e img.onload = function ()são encerrados, img fica inacessível e o lixo é coletado. Diferente do IE 6/7 e isso está ok.
humanityANDpeace
59

Vindo muito tempo depois, mas nenhuma das respostas aqui está totalmente correta.

Quando desenhada em uma tela, a imagem passada é descompactada + todas pré-multiplicada.
Quando exportado, é descompactado ou recomprimido com um algoritmo diferente e não é multiplicado.

Todos os navegadores e dispositivos terão diferentes erros de arredondamento nesse processo
(consulte Impressão digital do Canvas ).

Portanto, se alguém quiser uma versão base64 de um arquivo de imagem, precisará solicitá- lo novamente (na maioria das vezes será proveniente do cache), mas desta vez como um Blob.

Em seguida, você pode usar um FileReader para lê-lo como um ArrayBuffer ou 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
fonte
Acabado de passar por como 5+ perguntas diferentes e seu código é o único que trabalhou corretamente, obrigado :)
Peter
11
Eu recebo esse erro com o código acima. XMLHttpRequest cannot load data:image/jpeg;base64,/9j/4AA ... /2Q==. Invalid response. Origin 'https://example.com' is therefore not allowed access.
STWilson 2/17
2
@STWilson, sim, esse método também está vinculado à política de mesma origem, assim como a da tela. No caso de solicitação de origem cruzada, você precisa configurar o servidor de hospedagem para permitir essas solicitações de origem cruzada em seu script.
Kaiido 03/03
@Kaiido, então se a imagem estiver em outro servidor que não o script que o servidor de hospedagem precisa para ativar solicitações de origem cruzada? Se você definir img.setAttribute ('crossOrigin', 'anonymous'), isso evita o erro?
1,21 gigawatts
11
Após mais pesquisas, parece que as imagens podem usar o atributo crossOrigin para solicitar acesso, mas cabe ao servidor de hospedagem conceder acesso através de um cabeçalho CORS, sitepoint.com/an-in-depth-look-at-cors . Quanto ao XMLHttpRequest, parece que o servidor precisa acessar o mesmo que para imagens através de um cabeçalho CORS, mas nenhuma alteração é necessária no seu código, exceto talvez definir xhr.withCredentials como false (o padrão).
1,21 gigawatts
20

Uma versão mais moderna da resposta do kaiido usando a busca seria:

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 indicado nos comentários, isso retornará um URL de objeto que aponta para um arquivo no sistema local em vez de um DataURL real; portanto, dependendo do seu caso de uso, pode não ser o que você precisa.

Você pode olhar para a seguinte resposta para usar a busca e uma dataURL real: https://stackoverflow.com/a/50463054/599602

Zaptree
fonte
2
Não se esqueça de ligar URL.revokeObjectURL()se você tiver um aplicativo de execução demorada usando esse método, ou sua memória ficará cheia.
Engineer
11
Atenção: DataURL (base64 codificado) não é o mesmo que ObjectURL (referência a gota)
Daniell
11
O ObjectURL certamente não é um URL de dados. Esta não é uma resposta correta.
Danny Lin
2

shiv / shim / sham

Se suas imagens já estiverem carregadas (ou não), esta "ferramenta" pode 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);
    }}
);

.. mas por que?

Isso tem a vantagem de usar os dados de imagem "já carregados", portanto, nenhuma solicitação extra é necessária. Além disso, permite que o usuário final (programador como você) decida o CORS e / ou mime-typee quality-OR- você pode deixar de fora esses argumentos / parâmetros, conforme descrito na especificação MDN aqui .

Se você tiver esse JS carregado (antes de quando for necessário), a conversão para dataURLé tão simples quanto:

exemplos

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

Impressão digital da GPU

Se você está preocupado com a "precisão" dos bits, pode alterar esta ferramenta para atender às suas necessidades, conforme fornecido pela resposta do @ Kaiido.

argônio
fonte
1

Use o onloadevento para converter a imagem após o carregamento

Kamil Kiełczewski
fonte
-3

No HTML5, use melhor isso:

{
//...
canvas.width = img.naturalWidth; //img.width;
canvas.height = img.naturalHeight; //img.height;
//...
}
KepHec
fonte
2
Ao responder à pergunta, tente ser mais específico e fornecer explicações detalhadas.
Piyush Zalani