Obtenha a cor média da imagem via Javascript

118

Não tenho certeza se isso é possível, mas estou procurando escrever um script que retorne a média hexou o rgbvalor de uma imagem. Eu sei que isso pode ser feito em AS, mas procurando fazê-lo em JavaScript.

maior
fonte
19
meu amigo Boaz já percorreu esse caminho antes (espero que ele responda), mas a média geralmente deixa você com uma cor semelhante a vômito. O que você realmente quer é "cor dominante". Existem algumas maneiras de fazer isso (histograma de matiz com intervalo dinâmico, etc.), mas acho que é provavelmente mais preciso para o resultado pretendido.
Paul Irish,

Respostas:

149

AFAIK, a única maneira de fazer isso é com <canvas/>...

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

Observe que isso só funcionará com imagens no mesmo domínio e em navegadores que suportam tela 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;

}

Para o IE, verifique excanvas .

James
fonte
1
Existe alguma maneira de obter o histograma de cores para uma imagem localizada em um domínio diferente?
Dan Loewenherz
1
Dan: Acho que você encontra a restrição de origem cruzada se tentar acessar dados de imagem em uma imagem de outro domínio. O que é uma chatice.
8
Acho que há um posicionamento ruim para o valor azul e verde, você escreve 'rgb('+rgb.r+','+rgb.b+','+rgb.g+')'e deve ser 'rgb('+rgb.r+','+rgb.g+','+rgb.b+')'. é estranho quando a cor dominante é azul, mas o resultado é verde :).
Ariona Rian
7
Encontrou uma ótima solução alternativa para as restrições de origem cruzada: basta adicionar img.crossOrigin = '';antes de definir o srcatributo. Encontrado em: coderwall.com/p/pa-2uw
mhu
Não há necessidade de contar, pois count === length / 4 / blockSize;)
yckart
84

Decidi postar um projeto que descobri recentemente para obter a cor dominante:

Ladrão de cores

Um script para obter a cor dominante ou uma paleta de cores representativa de uma imagem. Usa javascript e canvas.

As outras soluções que mencionam e sugerem a cor dominante nunca respondem realmente à pergunta no contexto adequado ("em javascript"). Espero que este projeto ajude aqueles que desejam fazer exatamente isso.

J. Chase
fonte
55

"Cor dominante" é complicada. O que você deseja fazer é comparar a distância entre cada pixel e todos os outros pixels no espaço de cores (distância euclidiana) e, em seguida, encontrar o pixel cuja cor é mais próxima de todas as outras cores. Esse pixel é a cor dominante. A cor média geralmente será lama.

Gostaria de ter o MathML aqui para mostrar a distância euclidiana. Pesquise no Google.

Eu realizei a execução acima no espaço de cores RGB usando PHP / GD aqui: https://gist.github.com/cf23f8bddb307ad4abd8

No entanto, isso é muito caro do ponto de vista computacional. Irá travar seu sistema em imagens grandes e definitivamente travará seu navegador se você tentar no cliente. Tenho trabalhado na refatoração de minha execução para: - armazenar os resultados em uma tabela de pesquisa para uso futuro na iteração de cada pixel. - para dividir imagens grandes em grades de 20px 20px para dominância localizada. - para usar a distância euclidiana entre x1y1 e x1y2 para descobrir a distância entre x1y1 e x1y3.

Informe se você progredir nessa área. Eu ficaria feliz em ver isso. Eu vou fazer o mesmo.

O Canvas é definitivamente a melhor maneira de fazer isso no cliente. SVG não é, SVG é baseado em vetor. Depois de terminar a execução, a próxima coisa que quero fazer é executá-la na tela (talvez com um webworker para o cálculo da distância geral de cada pixel).

Outra coisa a se pensar é que RGB não é um bom espaço de cores para fazer isso, porque a distância euclidiana entre as cores no espaço RGB não é muito próxima da distância visual. Um espaço de cores melhor para fazer isso pode ser LUV, mas eu não encontrei uma boa biblioteca para isso, ou qualquer algoritmo de conversão de RGB para LUV.

Uma abordagem totalmente diferente seria classificar suas cores em um arco-íris e construir um histograma com tolerância para considerar os vários tons de uma cor. Não tentei fazer isso porque classificar as cores em um arco-íris é difícil, assim como os histogramas de cores. Eu posso tentar isso a seguir. Novamente, deixe-me saber se você fizer algum progresso aqui.


fonte
6
Esta é uma daquelas respostas pelas quais eu gostaria de poder fazer mais do que apenas +1 :-)
David Johnstone
3
Excelente resposta, embora uma solução clara não tenha sido apresentada. Isso vai me ajudar a decidir meu próximo passo ...
maior
Esta é uma ótima resposta. Eu escrevi alguns módulos de nó para fazer cálculos de distância euclidiana não em LUV, mas no espaço de cores L a b *. Consulte github.com/zeke/color-namer e github.com/zeke/euclidean-distance
Zeke
1
@user: Eu estava pensando: uma amostra de, digamos, 1% não seria suficiente em imagens grandes para acelerar a computação?
jackthehipster 01 de
Parece a mediana geométrica. Talvez isso possa ajudar? stackoverflow.com/questions/12934213/… e se o ponto que você está procurando deve existir no conjunto original: en.m.wikipedia.org/wiki/Medoid#Algorithms_to_compute_medoids
Lawrence Weru
16

Primeiro: isso pode ser feito sem HTML5 Canvas ou SVG.
Na verdade, alguém conseguiu gerar arquivos PNG do lado do cliente usando JavaScript , sem canvas ou SVG, usando o esquema de URI de dados .

Segundo: você pode realmente não precisar do Canvas, SVG ou qualquer um dos acima.
Se você só precisa processar as imagens no lado do cliente, sem modificá-las, tudo isso não é necessário.

Você pode obter o endereço de origem da tag img na página, fazer uma solicitação XHR para ele - provavelmente virá do cache do navegador - e processá-lo como um fluxo de bytes de Javascript.
Você precisará de um bom conhecimento do formato da imagem. (O gerador acima é parcialmente baseado em fontes libpng e pode fornecer um bom ponto de partida.)

Andras Vass
fonte
+1 para a solução bytestream. Essa é uma boa opção se a única opção for torná-la no aplicativo cliente.
Loretoparisi
11

Eu diria por meio da tag canvas HTML.

Você pode encontrar aqui um post de @Georg falando sobre um pequeno código do desenvolvedor do Opera:

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

Isso inverte a imagem usando os valores R, G e B de cada pixel. Você pode facilmente armazenar os valores RGB e, em seguida, arredondar as matrizes Red, Green e Blue e, finalmente, convertê-los de volta em um código HEX.

rnaud
fonte
alguma chance de haver uma alternativa do IE para a solução do canvas?
maior de
@ Ben4Himv: Existe o IE9 Platform Preview ;-) mas sério, infelizmente não.
Andy E
4

Javascript não tem acesso aos dados de cor de pixel individual de uma imagem. Pelo menos, talvez não até html5 ... nesse ponto, é lógico que você será capaz de desenhar uma imagem em uma tela e, em seguida, inspecionar a tela (talvez, eu nunca tenha feito isso sozinho).

Joel Martinez
fonte
2

Solução All-In-One

Eu pessoalmente combinaria o Color Thief junto com esta versão modificada de Name that Color para obter uma matriz mais do que suficiente de resultados de cores dominantes para imagens.

Exemplo:

Considere a seguinte imagem:

insira a descrição da imagem aqui

Você pode usar o seguinte código para extrair dados de imagem relacionados à cor dominante:

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
fonte
1

Trata-se de "Quantização de Cor" que tem várias abordagens como MMCQ (Quantização de Corte Mediana Modificada) ou OQ (Quantização Octree). Abordagens diferentes usam K-Means para obter grupos de cores.

Eu juntei tudo aqui, pois estava encontrando uma solução para tvOSonde há um subconjunto de XHTML que não tem <canvas/>elemento:

Gere as cores dominantes para uma imagem RGB com XMLHttpRequest

Loretoparisi
fonte
1

Maneira menos precisa, mas mais rápida de obter a cor média da imagem com dataurisuporte:

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
fonte
1

Como apontado em outras respostas, muitas vezes o que você realmente quer é a cor dominante em oposição à cor média que tende a ser marrom. Eu escrevi um script que obtém a cor mais comum e postei sobre esta essência

dctalbot
fonte
-2

Existe uma ferramenta online pickimagecolor.com que o ajuda a encontrar a média ou a cor dominante da imagem. Você só precisa fazer o upload de uma imagem do seu computador e clicar na imagem. Fornece a cor média em HEX, RGB e HSV. Ele também encontra os tons de cores correspondentes a essa cor para escolher. Eu usei várias vezes.

Shubham Agrawal
fonte