Jak naprawić błąd getImageData () Kanwa została skażona przez dane pochodzące z innych źródeł?

133

Mój kod działa bardzo dobrze na moim hoście lokalnym, ale nie działa w witrynie.

Otrzymałem ten błąd z konsoli, dla tej linii .getImageData(x,y,1,1).data:

Uncaught SecurityError: Failed to execute 'getImageData' on 'CanvasRenderingContext2D': The canvas has been tainted by cross-origin data. 

część mojego kodu:

jQuery.Event.prototype.rgb=function(){
        var x =  this.offsetX || (this.pageX - $(this.target).offset().left),y =  this.offsetY || (this.pageY - $(this.target).offset().top);
        if (this.target.nodeName!=="CANVAS")return null;
        return this.target.getContext('2d').getImageData(x,y,1,1).data;
    }

Uwaga: adres URL mojego obrazu (src) pochodzi z adresu URL subdomeny

Erfan Safarpoor
źródło
9
Otrzymuję ten błąd nawet wtedy, gdy img.src jest lokalnym względnym adresem URL: "img / foo.png" - więc o co chodzi z tym pochodzeniem?
Sanford Staab
Zobacz stackoverflow.com/a/16218015/5175433 : „Chrome nie uważa, że ​​różne pliki lokalne pochodzą z tej samej domeny”.
A_P

Odpowiedzi:

118

Jak powiedzieli inni, „plamiisz” płótno, ładując je z domeny o różnych źródłach.

https://developer.mozilla.org/en-US/docs/HTML/CORS_Enabled_Image

Możesz jednak temu zapobiec, ustawiając po prostu:

img.crossOrigin = "Anonymous";

Działa to tylko wtedy, gdy serwer zdalny odpowiednio ustawi następujący nagłówek:

Access-Control-Allow-Origin "*"

Wyboru pliku Dropbox przy użyciu opcji „Direct Link” jest doskonałym tego przykładem. Używam go na oddprints.com, aby przenieść obrazy ze zdalnego adresu URL skrzynki domyślnej do mojego obszaru roboczego, a następnie przesłać dane obrazu z powrotem na mój serwer. Wszystko w javascript

matowe oparzenia
źródło
1
To działało, aby naprawić ten błąd podczas ładowania obrazu imgur do jsfiddle.net
Travis J
3
zadziałało dla mnie, jeśli najpierw umieszczę crossorigin, a następnie ustawię źródło, a następnie uzyskam dostęp do danych
jones
3
Co jeśli jest to plik lokalny, a Twoja aplikacja w ogóle nie korzysta z internetu? Podobnie jak aplikacja webview na Androida, a obraz znajduje się w tym samym folderze co plik HTML. To nie rozwiązuje tego problemu.
Curtis
45

Okazało się, że muszę użyć .setAttribute('crossOrigin', '')i muszę dołączyć sygnaturę czasową do ciągu zapytania adresu URL, aby uniknąć odpowiedzi 304 bez Access-Control-Allow-Originnagłówka.

To mi daje

var url = 'http://lorempixel.com/g/400/200/';
var imgObj = new Image();
imgObj.src = url + '?' + new Date().getTime();
imgObj.setAttribute('crossOrigin', '');
Kirby
źródło
3
Wypróbowanie tego bez serwera w pliku lokalnym daje: Dostęp do obrazu w „file: /// C: /.../img/colorPaletteCtrl.png? 1507058959277” z pochodzenia „null” został zablokowany przez zasady CORS: nieprawidłowa odpowiedź . W związku z tym nie zezwala się na dostęp do źródła „null”.
Sanford Staab
1
Po prostu używając imgObj.setAttribute('crossOrigin', '')rozwiązałem to dla mnie - dzięki! img.crossOrigin = "Anonymous"działa również (jak wspomniano tutaj ). Przeglądarki @SanfordStaab traktują pliki lokalne jako znajdujące się w innej domenie, w przeciwnym razie właściciele witryn mogliby odczytać pliki lokalne. Musisz zażądać obrazów z tej samej domeny lub nagłówki w odpowiedzi na Twoje żądanie muszą informować przeglądarkę, że kod z innych domen może odczytać ten plik.
20

Nie będzie można rysować obrazów bezpośrednio z innego serwera na kanwie, a następnie używać getImageData. Jest to kwestia bezpieczeństwa, a płótno zostanie uznane za „skażone”.

Czy zadziałałoby, gdybyś zapisał kopię obrazu na serwerze za pomocą PHP, a następnie po prostu załadował nowy obraz? Na przykład możesz wysłać adres URL do skryptu PHP i zapisać go na swoim serwerze, a następnie zwrócić nową nazwę pliku do swojego javascript w następujący sposób:

<?php //The name of this file in this example is imgdata.php

  $url=$_GET['url'];

  // prevent hackers from uploading PHP scripts and pwning your system
  if(!@is_array(getimagesize($url))){
    echo "path/to/placeholderImage.png";
    exit("wrong file type.");
  }

  $img = file_get_contents($url);

  $fn = substr(strrchr($url, "/"), 1);
  file_put_contents($fn,$img);
  echo $fn;

?>

Używałbyś skryptu PHP z jakimś javascriptem Ajax w następujący sposób:

xi=new XMLHttpRequest();
xi.open("GET","imgdata.php?url="+yourImageURL,true);
xi.send();

xi.onreadystatechange=function() {
  if(xi.readyState==4 && xi.status==200) {
    img=new Image;
    img.onload=function(){ 
      ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
    }
    img.src=xi.responseText;
  }
}

Jeśli getImageDatapóźniej użyjesz na płótnie, będzie działać dobrze.

Alternatywnie, jeśli nie chcesz zapisywać całego obrazu, możesz przekazać współrzędne x & y do swojego skryptu PHP i obliczyć wartość rgba piksela po tej stronie. Myślę, że są dobre biblioteki do tego rodzaju przetwarzania obrazu w PHP.

Jeśli chcesz skorzystać z tego podejścia, daj mi znać, jeśli potrzebujesz pomocy w jego wdrożeniu.

edit-1: peeps wskazał, że skrypt php jest ujawniony i umożliwia internetowi potencjalnie złośliwe wykorzystanie go. istnieje milion sposobów, aby sobie z tym poradzić, jednym z najprostszych jest zaciemnianie adresów URL ... uważam, że bezpieczne praktyki php zasługują na osobne Google; P

edit-2: na popularne żądanie dodałem sprawdzenie, czy jest to obraz, a nie skrypt php (z: PHP sprawdź, czy plik jest obrazem ).

jaya
źródło
15
Jako dodatek - ładowanie obrazu i skryptu z lokalnego systemu plików powoduje ten sam problem.
Guy Dafny,
2
to niezbyt bezpieczne, ktoś mógłby złośliwie zalać twój serwer dużymi plikami przy użyciu skryptu php, co byłoby złe
LAX1DUDE
1
Odpowiedź @ LAX1DUDE poprawiona dzięki prostemu sprawdzeniu bezpieczeństwa
Jumpjack
1
dzięki tej metodzie możliwe jest również przekazanie obrazu do silnika OCR tesseract.js bez użycia node.js (wystarczy dodać wywołanie do Tesseract.recognize (img) w funkcji img.onload ().
jumpjack
1
POTRZEBUJESZ zweryfikować nazwę pliku, typ pliku / MIME, lokalizację i rozmiar przesyłanych plików dostarczonych przez użytkowników. Ten kod jest podatny na kilka ataków. Pozostawię to czytelnikowi jako ćwiczenie, ale uważam, że za pomocą tego kodu można przesłać pliki .php do katalogu głównego serwera internetowego.
hiburn8
4

Widziałem ten błąd Chromepodczas lokalnego testowania kodu. Przerzuciłem się naFirefox i już nie widzę błędu. Może przejście na inną przeglądarkę to szybkie rozwiązanie.

Jeśli korzystasz z rozwiązania podanego w pierwszej odpowiedzi, to upewnij się, że dodajesz img.crossOrigin = "Anonymous";zaraz po zadeklarowaniu imgzmiennej (np. var img = new Image();).

Safwan
źródło
2

Twój problem polega na tym, że ładujesz obraz zewnętrzny, czyli z innej domeny. Powoduje to błąd zabezpieczeń podczas próby uzyskania dostępu do jakichkolwiek danych kontekstu kanwy.

axelduch
źródło
tak, otrzymuję obraz z subdomeny przesyłania ... Co mam zrobić?
Erfan Safarpoor
1
Cóż, możesz użyć serwera proxy do pobrania obrazu, ten serwer proxy byłby w tej samej domenie, co ta, na której używasz płótna.
axelduch
1
Przesyłam nowy obraz z mojego komputera i próbuję załadować go na płótnie i otrzymuję ten błąd. Nie używam żadnego typu obrazu serwera.
Piyush Dholariya
Twój komputer jest postrzegany przez serwer, na którym znajduje się skrypt (ale nie jest wykonywany), jako domena zewnętrzna, ponieważ obraz kopiowany do kanwy jest tworzony na komputerze, na którym skrypt jest faktycznie wykonywany.
Jumpjack
2

„Plamiisz” płótno, ładując je z domeny o różnych źródłach. Przeczytaj ten artykuł w MDN:

https://developer.mozilla.org/en-US/docs/HTML/CORS_Enabled_Image

srquinn
źródło
Dodałem <IfModule mod_setenvif.c> <IfModule mod_headers.c> <FilesMatch "\. (cur | gif | ico | jpe? g | png | svgz? | webp) $"> SetEnvIf Origin ":" IS_CORS Header set Access- Control-Allow-Origin "*" env = IS_CORS </FilesMatch> </IfModule> </IfModule>, aby uzyskać dostęp do wszystkich domen, ale nie działa!
Erfan Safarpoor
1
W jakiej przeglądarce testujesz? I upewnij się, że ponownie uruchomiłeś Apache.
srquinn
nie mogę zrestartować Apache ... mam współdzielone konto cpanel.
Erfan Safarpoor
2
Nie mogę pomóc w skonfigurowaniu konfiguracji Apache, przepraszam. To również nie wchodzi w zakres tego pytania. Opublikuj swoje problemy związane z konfiguracją Apache na nowe pytanie, a społeczność z przyjemnością Ci w tym pomoże.
srquinn
2

Podczas pracy na lokalnym dodaj serwer

Miałem podobny problem podczas pracy na lokalnym. Twój adres URL będzie ścieżką do lokalnego pliku, np. File: ///Users/PeterP/Desktop/folder/index.html.

Pamiętaj, że korzystam z komputera MAC.

Poradziłem sobie z tym, instalując globalnie serwer http. https://www.npmjs.com/package/http-server

Kroki:

  1. Instalacja globalna: npm install http-server -g
  2. Uruchom serwer: http-server ~/Desktop/folder/

PS: Zakładam, że masz zainstalowany węzeł, w przeciwnym razie nie uzyskasz bardzo działających poleceń npm.

Anas
źródło
Doceniam to. Dobra robota. Szkoda, że ​​musi istnieć obejście, jeśli chcesz po prostu czerpać przyjemność z kodowania jako hobby i uczynić swoje życie łatwiejszym i lepszym. Tyle obręczy, które wydają się niepotrzebne. To tylko przykład. westchnij ...
Bryan
0

Jak Matt Burns mówi w swojej odpowiedzi , być może trzeba będzie włączyć CORS na serwerze, na którym są hostowane obrazy powodujące problemy.

Jeśli serwer to Apache, można to zrobić, dodając następujący fragment kodu ( stąd ) do konfiguracji VirtualHost lub do .htaccesspliku:

<IfModule mod_setenvif.c>
    <IfModule mod_headers.c>
        <FilesMatch "\.(cur|gif|ico|jpe?g|png|svgz?|webp)$">
            SetEnvIf Origin ":" IS_CORS
            Header set Access-Control-Allow-Origin "*" env=IS_CORS
        </FilesMatch>
    </IfModule>
</IfModule>

... jeśli dodasz go do VirtualHost, prawdopodobnie będziesz musiał ponownie załadować konfigurację Apache (np. sudo service apache2 reloadjeśli Apache działa na serwerze Linux)

Nick F.
źródło
0

Mój problem był tak pomieszany, że po prostu zakodowałem obraz w base64, aby upewnić się, że nie ma żadnych problemów z CORS

bumsoverboard
źródło
-2

Dziś napotykam ten sam problem i rozwiązuję go za pomocą poniższego kodu.

Kod HTML:

<div style='display: none'>
  <img id='img' src='img/iak.png' width='600' height='400' />
</div>
<canvas id='iak'>broswer don't support canvas</canvas>

kod js:

var canvas = document.getElementById('iak')
var iakImg = document.getElementById('img')
var ctx = canvas.getContext('2d')
var image = new Image()
image.src=iakImg.src
image.onload = function () {
   ctx.drawImage(image,0,0)
   var data = ctx.getImageData(0,0,600,400)
}

kod jak powyżej i nie ma problemu między domenami.

Karry
źródło
1
Wartość src nadal jest źródłem z różnych źródeł, więc jak to uniknąć problemu?
Loveen Dyall
To podejście działa w przypadku uruchamiania lokalnego pliku html (bez serwera), takiego jak file: /// X: /htmlfile.html. Większość ludzi kończy swoje wypowiedzi znakiem „;”. Jaki jest sens instrukcji „var data = ..”? Chciałbym WYJAŚNIĆ, DLACZEGO to pozwala uniknąć błędu „międzydomenowego”. Ale tak jest, dzięki.
dcromley
double smart karry
Mari Selvan
1
@dcromley "Większość ludzi kończy swoje wypowiedzi;" Zgodnie ze standardami nie zaleca się używania średników. Średniki po prostu dodają więcej pracy, wyglądają brzydko, a kod działa bez nich doskonale. Sprawdź github.com/standard/standard
madprops
1
@madprops developer.mozilla.org mówi: „Aplikacje JavaScript składają się z instrukcji o odpowiedniej składni. Pojedyncza instrukcja może obejmować wiele wierszy. Jeśli każda instrukcja jest oddzielona średnikiem, może wystąpić wiele instrukcji w jednym wierszu. To nie jest słowo kluczowe , ale grupa słów kluczowych ”. Jestem więc we frakcji „używaj zawsze”. Nie wiedziałem nawet, że istnieje frakcja „nigdy”. Zakładam, że ci drudzy również uważają, że ziemia jest płaska? (
Żartuję
-3

Miałem ten sam problem i dla mnie zadziałało to po prostu łącząc https:${image.getAttribute('src')}

Livia Rett
źródło
proszę wyjaśnij, co zrobiłeś
Yehuda Schwartz