Uzyskaj adres URL obrazu w JavaScript?

335

Mam zwykłą stronę HTML z kilkoma obrazkami (tylko zwykłe <img />tagi HTML). Chciałbym uzyskać ich zawartość, najlepiej zakodowaną w standardzie base64, bez potrzeby ponownego pobierania obrazu (tzn. Jest już załadowany przez przeglądarkę, więc teraz chcę zawartość).

Chciałbym to osiągnąć dzięki Greasemonkey i Firefox.

Detariael
źródło

Odpowiedzi:

400

Uwaga: Działa to tylko wtedy, gdy obraz pochodzi z tej samej domeny co strona lub ma crossOrigin="anonymous"atrybut, a serwer obsługuje CORS. Nie da ci również oryginalnego pliku, ale wersję ponownie zakodowaną. Jeśli chcesz, aby wynik był identyczny z oryginałem, zobacz odpowiedź Kaiido .


Musisz utworzyć element canvas o odpowiednich wymiarach i skopiować dane obrazu za pomocą drawImagefunkcji. Następnie możesz użyć tej toDataURLfunkcji, aby uzyskać dane: adres URL zawierający obraz zakodowany w standardzie 64. Pamiętaj, że obraz musi być w pełni załadowany, w przeciwnym razie otrzymasz tylko pusty (czarny, przezroczysty) obraz.

To byłoby coś takiego. Nigdy nie napisałem skryptu Greasemonkey, więc może być konieczne dostosowanie kodu, aby działał w tym środowisku.

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

Pobieranie obrazu w formacie JPEG nie działa na starszych wersjach przeglądarki Firefox (około 3.5), więc jeśli chcesz to obsługiwać, musisz sprawdzić kompatybilność. Jeśli kodowanie nie jest obsługiwane, domyślnie będzie to „image / png”.

Matthew Crumley
źródło
1
Chociaż wydaje się, że to działa (oprócz nieokreślonego / w zamian), nie tworzy tego samego ciągu base64, jak ten, który otrzymuję z PHP podczas wykonywania kodu base64_encode na pliku uzyskanym za pomocą funkcji file_get_contents. Obrazy wydają się bardzo podobne / takie same, ale Javascript jest mniejszy i chciałbym, żeby były dokładnie takie same. Jeszcze jedno: obraz wejściowy to mały (594 bajty), plik PNG 28x30 z przezroczystym tłem - jeśli to coś zmieni.
Detariael
7
Firefox może używać innego poziomu kompresji, który wpływałby na kodowanie. Myślę też, że PNG obsługuje dodatkowe informacje nagłówka, takie jak notatki, które zostałyby utracone, ponieważ płótno pobiera tylko dane pikseli. Jeśli potrzebujesz, aby był dokładnie taki sam, prawdopodobnie możesz użyć AJAX, aby uzyskać plik i kodować go base64 ręcznie.
Matthew Crumley
7
Tak, możesz pobrać obraz za pomocą XMLHttpRequest. Mamy nadzieję, że użyłby wersji obrazu z pamięci podręcznej, ale zależałoby to od konfiguracji serwera i przeglądarki, a narzut miałby narzut w celu ustalenia, czy plik się zmienił. Dlatego nie sugerowałem tego przede wszystkim :-) Niestety, o ile wiem, jest to jedyny sposób na uzyskanie oryginalnego pliku.
Matthew Crumley
2
@trusktr Nie drawImagezrobi nic, a skończysz na pustym płótnie i obrazie wynikowym.
Matthew Crumley
1
@JohnSewell To tylko dlatego, że OP chciał treści, a nie adresu URL. Musisz pominąć wywołanie replace (...), jeśli chcesz użyć go jako źródła obrazu.
Matthew Crumley,
76

Ta funkcja pobiera adres URL, a następnie zwraca obraz 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;
}

Nazwij to tak: getBase64FromImageUrl("images/slbltxt.png")

MuniR
źródło
8
Czy istnieje sposób na przekształcenie obrazu z zewnętrznego łącza do bazy 64?
dinodsaurus
@dinodsaurus możesz załadować go do znacznika obrazu lub wykonać zapytanie ajax.
Jared Forsyth
6
Cóż, wymaga to od nich implementacji CORS, ale wtedy wystarczy zrobić $ .ajax (theimageurl) i powinien zwrócić coś rozsądnego. W przeciwnym razie (jeśli nie mają włączonego CORS) to nie zadziała; model bezpieczeństwa Internetu nie pozwala na to. Chyba, że ​​oczywiście jesteś w chromowanej wtyczce, w którym to przypadku wszystko jest dozwolone - nawet powyższy przykład
Jared Forsyth
4
Musisz umieścić img.src =później img.onload =, ponieważ w niektórych przeglądarkach, takich jak Opera, wydarzenie się nie wydarzy.
ostapische
1
@Hakkar wyciek pamięci wystąpi tylko w starych przeglądarkach, które używają wciąż licznika odwołań do informowania o śmieciach. O ile mi wiadomo, okrągłe odniesienie nie powoduje przecieku w konfiguracji znaku i wyciągnięcia po ścieżce. Po wyjściu z zakresu getBase64FromImageUrl(url)i img.onload = function ()opuszczeniu funkcji img jest nieosiągalny, a śmieci są gromadzone. Inne niż IE 6/7 i jest w porządku.
humanityANDpeace
59

Wkrótce potem, ale żadna z odpowiedzi tutaj nie jest całkowicie poprawna.

Po narysowaniu na płótnie przekazany obraz jest nieskompresowany + wszystkie wstępnie pomnożone.
Po wyeksportowaniu jest nieskompresowany lub ponownie skompresowany przy użyciu innego algorytmu i niezmnożony.

Wszystkie przeglądarki i urządzenia będą miały różne błędy zaokrąglania występujące w tym procesie
(patrz Odcisk palca na płótnie ).

Więc jeśli ktoś chce plik obrazu w wersji base64, musi poprosić go ponownie (przez większość czasu będzie pochodził z pamięci podręcznej), ale tym razem jako obiekt Blob.

Następnie można użyć czytnika plików, aby odczytać go jako ArrayBuffer lub jako 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
źródło
Właśnie przeszedłem ponad 5 różnych pytań, a Twój kod jest jedynym, który działał poprawnie, dzięki :)
Peter
1
Otrzymuję ten błąd z powyższym kodem. XMLHttpRequest cannot load data:image/jpeg;base64,/9j/4AA ... /2Q==. Invalid response. Origin 'https://example.com' is therefore not allowed access.
STWilson
2
@STWilson, tak, ta metoda jest również powiązana z polityką tego samego pochodzenia, podobnie jak w przypadku metody canvas. W przypadku żądania pochodzącego z różnych źródeł należy skonfigurować serwer hostingowy, aby zezwalał na takie żądania dotyczące pochodzenia w skrypcie.
Kaiido
@Kaiido, więc jeśli obraz znajduje się na innym serwerze niż skrypt, serwer hostingowy musi włączyć żądania pochodzenia krzyżowego? Jeśli ustawisz img.setAttribute („crossOrigin”, „anonymous”), czy to zapobiegnie błędowi?
1,21 gigawatów
1
Po dalszych badaniach wygląda na to, że obrazki mogą użyć atrybutu crossOrigin, aby poprosić o dostęp, ale to serwer hostingowy zapewnia dostęp przez nagłówek CORS, sitepoint.com/an-in-depth-look-at-cors . Jeśli chodzi o XMLHttpRequest, wygląda na to, że serwer musi dawać dostęp tak samo, jak w przypadku obrazów przez nagłówek CORS, ale nie jest wymagana żadna zmiana w kodzie, z wyjątkiem ustawienia wartości xhr.withCredentials na false (domyślnie).
1,21 gigawatów
20

Nowocześniejszą wersją odpowiedzi kaiido z użyciem funkcji pobierania byłoby:

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

Edycja: Jak wskazano w komentarzach, zwróci adres URL obiektu, który wskazuje plik w twoim systemie lokalnym zamiast rzeczywistej DataURL, więc w zależności od przypadku użycia może nie być to, czego potrzebujesz.

Możesz spojrzeć na następującą odpowiedź, aby użyć funkcji pobierania i rzeczywistych danychURL: https://stackoverflow.com/a/50463054/599602

Zaptree
źródło
2
Nie zapomnij zadzwonić, URL.revokeObjectURL()jeśli masz długo działającą aplikację korzystającą z tej metody, w przeciwnym razie pamięć się zapełni.
Inżynier
1
Uwaga: DataURL (zakodowane w base64) nie jest tym samym co ObjectURL (odniesienie do obiektu blob)
DaniEll,
1
ObjectURL z pewnością nie jest adresem URL danych. To nie jest poprawna odpowiedź.
Danny Lin
2

Shiv / Shim / Sham

Jeśli twoje obrazy są już załadowane (lub nie), to „narzędzie” może się przydać:

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

.. ale dlaczego?

Ma to tę zaletę, że wykorzystuje „już załadowane” dane obrazu, więc nie jest wymagane dodatkowe żądanie. Aditionally że pozwala użytkownikowi końcowemu (programista jak ty) decyduje się CORS i / lub mime-typei quality-LUB- można pominąć te argumenty / parametry opisane w MDN specyfikacji tutaj .

Jeśli masz ten JS załadowany (przed, kiedy jest potrzebny), wówczas konwersja do dataURLjest tak prosta, jak:

przykłady

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

Odcisk cyfrowy GPU

Jeśli obawiasz się o „dokładność” bitów, możesz zmienić to narzędzie, aby dostosować je do swoich potrzeb, zgodnie z odpowiedzią @ Kaiido.

argon
źródło
1

Użyj onloadzdarzenia do konwersji obrazu po załadowaniu

Kamil Kiełczewski
źródło
-3

W HTML5 lepiej użyj tego:

{
//...
canvas.width = img.naturalWidth; //img.width;
canvas.height = img.naturalHeight; //img.height;
//...
}
KepHec
źródło
2
Odpowiadając na pytanie, staraj się być bardziej szczegółowy i podać szczegółowe wyjaśnienia.
Piyush Zalani