Uzyskaj średni kolor obrazu za pomocą JavaScript

118

Nie jestem pewien, czy jest to możliwe, ale chciałbym napisać skrypt, który zwróciłby średnią hexlub rgbwartość obrazu. Wiem, że można to zrobić w AS, ale chcę to zrobić w JavaScript.

bgreater
źródło
19
mój przyjaciel Boaz już wcześniej podążał tą ścieżką (mam nadzieję, że wtrąca się), ale przeciętny kolor zazwyczaj pozostawia na tobie kolor przypominający wymiociny. To, czego naprawdę chcesz, to „dominujący kolor”. Jest kilka sposobów, aby to uzyskać (histogram odcieni z dynamicznym podziałem itp.), Ale myślę, że jest to prawdopodobnie bardziej precyzyjne dla zamierzonego wyniku.
Paul Irish,

Odpowiedzi:

149

AFAIK, jedynym sposobem na to jest <canvas/>...

DEMO V2 : http://jsfiddle.net/xLF38/818/

Uwaga, będzie to działać tylko w przypadku obrazów w tej samej domenie i w przeglądarkach obsługujących kanwę HTML5:

function getAverageRGB(imgEl) {

    var blockSize = 5, // only visit every 5 pixels
        defaultRGB = {r:0,g:0,b:0}, // for non-supporting envs
        canvas = document.createElement('canvas'),
        context = canvas.getContext && canvas.getContext('2d'),
        data, width, height,
        i = -4,
        length,
        rgb = {r:0,g:0,b:0},
        count = 0;

    if (!context) {
        return defaultRGB;
    }

    height = canvas.height = imgEl.naturalHeight || imgEl.offsetHeight || imgEl.height;
    width = canvas.width = imgEl.naturalWidth || imgEl.offsetWidth || imgEl.width;

    context.drawImage(imgEl, 0, 0);

    try {
        data = context.getImageData(0, 0, width, height);
    } catch(e) {
        /* security error, img on diff domain */
        return defaultRGB;
    }

    length = data.data.length;

    while ( (i += blockSize * 4) < length ) {
        ++count;
        rgb.r += data.data[i];
        rgb.g += data.data[i+1];
        rgb.b += data.data[i+2];
    }

    // ~~ used to floor values
    rgb.r = ~~(rgb.r/count);
    rgb.g = ~~(rgb.g/count);
    rgb.b = ~~(rgb.b/count);

    return rgb;

}

W przypadku IE sprawdź excanvas .

James
źródło
1
Czy istnieje sposób na uzyskanie histogramu kolorów dla obrazu znajdującego się w innej domenie?
Dan Loewenherz
1
Dan: Myślę, że napotkasz ograniczenie między źródłami, jeśli spróbujesz uzyskać dostęp do danych obrazu na obrazie z innej domeny. Co jest kłopotliwe.
8
myślę, że jest miejsce na niebieskie i zielone wartości, piszesz 'rgb('+rgb.r+','+rgb.b+','+rgb.g+')'i tak powinno być 'rgb('+rgb.r+','+rgb.g+','+rgb.b+')'. to dziwne, gdy dominującym kolorem jest niebieski, a wynik jest zielony :).
Ariona Rian
7
Znalazłem świetne obejście ograniczeń między źródłami: po prostu dodaj img.crossOrigin = '';przed ustawieniem srcatrybutu. Znaleziono na: coderwall.com/p/pa-2uw
mhu,
Nie trzeba liczyć, ponieważ count === length / 4 / blockSize;)
yckart
84

Pomyślałem, że opublikuję projekt, na który niedawno natknąłem się, aby uzyskać dominujący kolor:

Złodziej kolorów

Skrypt do pobierania dominującego koloru lub reprezentatywnej palety kolorów z obrazu. Używa javascript i canvas.

Inne rozwiązania wspominające i sugerujące dominujący kolor nigdy tak naprawdę nie odpowiadają na pytanie w odpowiednim kontekście („in javascript”). Mam nadzieję, że ten projekt pomoże tym, którzy chcą to zrobić.

J. Chase
źródło
55

„Dominant Color” jest trudny. To, co chcesz zrobić, to porównać odległość między każdym pikselem a każdym innym pikselem w przestrzeni kolorów (odległość euklidesowa), a następnie znaleźć piksel, którego kolor jest najbliższy każdemu innemu kolorowi. Ten piksel jest kolorem dominującym. Przeciętny kolor to zwykle błoto.

Żałuję, że nie mam tutaj MathML, aby pokazać odległość euklidesową. Wygoogluj to.

Powyższe wykonanie wykonałem w przestrzeni kolorów RGB przy użyciu PHP / GD tutaj: https://gist.github.com/cf23f8bddb307ad4abd8

Jest to jednak bardzo kosztowne obliczeniowo. Spowoduje to awarię systemu przy dużych obrazach i na pewno spowoduje awarię przeglądarki, jeśli spróbujesz tego w kliencie. Pracowałem nad refaktoryzacją mojego wykonania, aby: - ​​przechowywać wyniki w tabeli przeglądowej do wykorzystania w przyszłości w iteracji po każdym pikselu. - dzielenie dużych obrazów na siatki o wymiarach 20px 20px w celu zlokalizowanej dominacji. - użyć odległości euklidesowej między x1y1 i x1y2, aby obliczyć odległość między x1y1 i x1y3.

Daj mi znać, jeśli zrobisz postęp w tej dziedzinie. Z przyjemnością to zobaczę. Zrobię to samo.

Canvas to zdecydowanie najlepszy sposób na zrobienie tego w kliencie. SVG nie jest, SVG jest oparty na wektorach. Po zakończeniu wykonywania następną rzeczą, którą chcę zrobić, jest uruchomienie tego w płótnie (może z pracownikiem sieciowym do obliczenia całkowitej odległości każdego piksela).

Inną rzeczą do przemyślenia jest to, że RGB nie jest dobrą przestrzenią kolorów do tego, ponieważ odległość euklidesowa między kolorami w przestrzeni RGB nie jest zbyt bliska odległości wizualnej. Lepszą przestrzenią kolorów do tego może być LUV, ale nie znalazłem do tego dobrej biblioteki ani żadnych algorytmów konwersji RGB na LUV.

Zupełnie innym podejściem byłoby posortowanie kolorów w tęczy i utworzenie histogramu z tolerancją uwzględniającą różne odcienie koloru. Nie próbowałem tego, ponieważ sortowanie kolorów w tęczy jest trudne, podobnie jak histogramy kolorów. Mogę spróbować jako następny. Ponownie, daj mi znać, jeśli zrobisz tutaj jakiś postęp.


źródło
6
To jedna z tych odpowiedzi, dla których chciałbym móc zrobić więcej niż tylko +1 :-)
David Johnstone
3
Doskonała odpowiedź, mimo że nie przedstawiono jasnego rozwiązania. Pomoże mi to w podjęciu decyzji o kolejnym kroku ...
bgreater
To jest świetna odpowiedź. Napisałem moduł kilku węzłów, aby obliczyć odległość euklidesową nie w LUV, ale w przestrzeni kolorów L a b *. Zobacz github.com/zeke/color-namer i github.com/zeke/euclidean-distance
Zeke
1
@user: Zastanawiałem się tylko: czy nie wystarczy, powiedzmy, 1% próbki na dużych obrazach, aby przyspieszyć obliczenia?
jackthehipster
Brzmi jak geometryczna mediana. Może to może pomóc? stackoverflow.com/questions/12934213/… i jeśli punkt, którego szukasz, musi istnieć w oryginalnym zestawie: en.m.wikipedia.org/wiki/Medoid#Algorithms_to_compute_medoids
Lawrence Weru
16

Po pierwsze: można to zrobić bez HTML5 Canvas lub SVG.
W rzeczywistości komuś udało się wygenerować pliki PNG po stronie klienta za pomocą JavaScript , bez płótna lub SVG, używając schematu URI danych .

Po drugie: możesz w ogóle nie potrzebować Canvas, SVG ani żadnego z powyższych.
Jeśli potrzebujesz tylko przetwarzać obrazy po stronie klienta, bez ich modyfikowania, wszystko to nie jest potrzebne.

Możesz pobrać adres źródłowy z tagu img na stronie, wysłać żądanie XHR - najprawdopodobniej będzie pochodzić z pamięci podręcznej przeglądarki - i przetworzyć go jako strumień bajtów z Javascript.
Będziesz potrzebować dobrego zrozumienia formatu obrazu. (Powyższy generator jest częściowo oparty na źródłach libpng i może stanowić dobry punkt wyjścia).

Andras Vass
źródło
+1 dla rozwiązania bytestream. Jest to dobre rozwiązanie, jeśli jedyną opcją jest zrobienie tego w aplikacji klienckiej.
loretoparisi
11

Powiedziałbym, że poprzez tag HTML canvas.

Możesz znaleźć tutaj post @Georg, który mówi o małym kodzie autora Opery:

// Get the CanvasPixelArray from the given coordinates and dimensions.
var imgd = context.getImageData(x, y, width, height);
var pix = imgd.data;

// Loop over each pixel and invert the color.
for (var i = 0, n = pix.length; i < n; i += 4) {
    pix[i  ] = 255 - pix[i  ]; // red
    pix[i+1] = 255 - pix[i+1]; // green
    pix[i+2] = 255 - pix[i+2]; // blue
    // i+3 is alpha (the fourth element)
}

// Draw the ImageData at the given (x,y) coordinates.
context.putImageData(imgd, x, y);

To odwraca obraz, używając wartości R, G i B każdego piksela. Możesz łatwo zapisać wartości RGB, a następnie zaokrąglić tablice czerwonego, zielonego i niebieskiego, a na koniec przekonwertować je z powrotem na kod HEX.

rnaud
źródło
jest szansa, że ​​istnieje alternatywa IE dla rozwiązania Canvas?
bgreater
@ Ben4Himv: Jest podgląd platformy IE9 ;-), ale poważnie, obawiam się, że nie.
Andy E
4

Javascript nie ma dostępu do danych koloru poszczególnych pikseli obrazu. Przynajmniej może nie do html5 ... w tym momencie ma się rozumieć, że będziesz mógł narysować obraz na płótnie, a następnie sprawdzić płótno (być może nigdy tego nie robiłem).

Joel Martinez
źródło
2

Rozwiązanie typu „wszystko w jednym”

Osobiście połączyłbym Złodzieja Kolorów z tą zmodyfikowaną wersją Nazwy tego Koloru, aby uzyskać więcej niż wystarczający zestaw dominujących kolorów dla obrazów.

Przykład:

Rozważ następujący obraz:

wprowadź opis obrazu tutaj

Możesz użyć następującego kodu, aby wyodrębnić dane obrazu związane z dominującym kolorem:

let color_thief = new ColorThief();
let sample_image = new Image();

sample_image.onload = () => {
  let result = ntc.name('#' + color_thief.getColor(sample_image).map(x => {
    const hex = x.toString(16);
    return hex.length === 1 ? '0' + hex : hex;
    
  }).join(''));
  
  console.log(result[0]); // #f0c420     : Dominant HEX/RGB value of closest match
  console.log(result[1]); // Moon Yellow : Dominant specific color name of closest match
  console.log(result[2]); // #ffff00     : Dominant HEX/RGB value of shade of closest match
  console.log(result[3]); // Yellow      : Dominant color name of shade of closest match
  console.log(result[4]); // false       : True if exact color match
};

sample_image.crossOrigin = 'anonymous';
sample_image.src = document.getElementById('sample-image').src;
Grant Miller
źródło
1

Chodzi o „kwantyzację koloru”, która ma kilka podejść, takich jak MMCQ (zmodyfikowana kwantyzacja mediany cięcia) lub OQ (kwantyzacja ośmiokrotnie). Inne podejście wykorzystuje K-Means do uzyskania klastrów kolorów.

Złożyłem wszystko razem, ponieważ szukałem rozwiązania dla tvOSmiejsca, w którym istnieje podzbiór XHTML, który nie ma <canvas/>elementu:

Wygeneruj dominujące kolory dla obrazu RGB za pomocą XMLHttpRequest

loretoparisi
źródło
1

Mniej dokładny, ale najszybszy sposób na uzyskanie przeciętnego koloru obrazu z datauriobsługą:

function get_average_rgb(img) {
    var context = document.createElement('canvas').getContext('2d');
    if (typeof img == 'string') {
        var src = img;
        img = new Image;
        img.setAttribute('crossOrigin', ''); 
        img.src = src;
    }
    context.imageSmoothingEnabled = true;
    context.drawImage(img, 0, 0, 1, 1);
    return context.getImageData(1, 1, 1, 1).data.slice(0,3);
}
350D
źródło
1

Jak wskazano w innych odpowiedziach, często to, czego naprawdę chcesz, to kolor dominujący, w przeciwieństwie do przeciętnego koloru, który jest zwykle brązowy. Napisałem skrypt, który pobiera najczęściej spotykany kolor i umieściłem go w tym temacie

dctalbot
źródło
-2

Istnieje internetowe narzędzie pickimagecolor.com, które pomaga znaleźć średni lub dominujący kolor obrazu. Wystarczy załadować obraz z komputera, a następnie kliknąć go. Daje średni kolor w HEX, RGB i HSV. Znajduje również odcienie pasujące do tego koloru do wyboru. Używałem go wiele razy.

shubham agrawal
źródło