Obtenga el color promedio de la imagen a través de Javascript

118

No estoy seguro de que esto sea posible, pero estoy buscando escribir un script que devuelva el promedio hexo el rgbvalor de una imagen. Sé que se puede hacer en AS pero estoy buscando hacerlo en JavaScript.

bgreater
fuente
19
mi amigo Booz ha estado en este camino antes (con suerte él interviene) pero el promedio generalmente te deja con un color parecido al de un vómito. Lo que realmente quieres es "color dominante". Hay algunas formas de conseguirlo (histograma de tono con agrupamiento dinámico, etc.) pero creo que probablemente sea más preciso para el resultado deseado.
Paul Irish

Respuestas:

149

AFAIK, la única forma de hacer esto es con <canvas/>...

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

Tenga en cuenta que esto solo funcionará con imágenes en el mismo dominio y en navegadores que admitan el lienzo 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 IE, consulte excanvas .

James
fuente
1
¿Hay alguna forma de obtener el histograma de color para una imagen que se encuentra en un dominio diferente?
Dan Loewenherz
1
Dan: Creo que te encuentras con la restricción de origen cruzado si intentas acceder a los datos de una imagen de otro dominio. Lo cual es un fastidio.
8
Creo que hay una buena ubicación para el valor azul y verde, escribe 'rgb('+rgb.r+','+rgb.b+','+rgb.g+')'y debería ser 'rgb('+rgb.r+','+rgb.g+','+rgb.b+')'. es extraño cuando el color dominante es el azul pero el resultado es el verde :).
Ariona Rian
7
Encontré una excelente solución para las restricciones de origen cruzado: simplemente agregue img.crossOrigin = '';antes de establecer el srcatributo. Se encuentra en: coderwall.com/p/pa-2uw
mhu
No es necesario contar, ya que count === length / 4 / blockSize;)
yckart
84

Pensé que publicaría un proyecto que encontré recientemente para obtener un color dominante:

Ladrón de colores

Un guión para tomar el color dominante o una paleta de colores representativa de una imagen. Utiliza javascript y lienzo.

Las otras soluciones que mencionan y sugieren el color dominante nunca realmente responden a la pregunta en el contexto adecuado ("en javascript"). Con suerte, este proyecto ayudará a quienes quieran hacer precisamente eso.

J. Chase
fuente
55

El "color dominante" es complicado. Lo que quiere hacer es comparar la distancia entre cada píxel y todos los demás píxeles en el espacio de color (Distancia euclidiana) y luego encontrar el píxel cuyo color es más cercano a cualquier otro color. Ese píxel es el color dominante. El color medio suele ser barro.

Ojalá tuviera MathML aquí para mostrarte la distancia euclidiana. Buscalo en Google.

He logrado la ejecución anterior en el espacio de color RGB usando PHP / GD aquí: https://gist.github.com/cf23f8bddb307ad4abd8

Sin embargo, esto es muy costoso computacionalmente. Bloqueará su sistema en imágenes grandes y definitivamente bloqueará su navegador si lo prueba en el cliente. He estado trabajando en refactorizar mi ejecución para: - almacenar resultados en una tabla de búsqueda para uso futuro en la iteración sobre cada píxel. - para dividir imágenes grandes en cuadrículas de 20px 20px para un dominio localizado. - utilizar la distancia euclidiana entre x1y1 y x1y2 para calcular la distancia entre x1y1 y x1y3.

Por favor, avíseme si progresa en este frente. Estaría feliz de verlo. Voy a hacer lo mismo.

Canvas es definitivamente la mejor manera de hacer esto en el cliente. SVG no, SVG está basado en vectores. Después de bajar la ejecución, lo siguiente que quiero hacer es ejecutar esto en el lienzo (tal vez con un webworker para el cálculo de la distancia total de cada píxel).

Otra cosa en la que pensar es que RGB no es un buen espacio de color para hacer esto, porque la distancia euclidiana entre colores en el espacio RGB no está muy cerca de la distancia visual. Un mejor espacio de color para hacer esto podría ser LUV, pero no he encontrado una buena biblioteca para esto, ni ningún algoritmo para convertir RGB a LUV.

Un enfoque completamente diferente sería ordenar los colores en un arco iris y construir un histograma con tolerancia para tener en cuenta los diferentes tonos de un color. No lo he intentado, porque clasificar los colores en un arco iris es difícil, al igual que los histogramas de colores. Podría intentar esto a continuación. Nuevamente, avíseme si logra algún progreso aquí.


fuente
6
Esta es una de esas respuestas por las que me gustaría poder hacer más que solo +1 :-)
David Johnstone
3
Excelente respuesta a pesar de que no se proporcionó una solución clara. Esto me ayudará a decidir mi próximo paso ...
bgreater
Esta es una gran respuesta. Escribí algunos módulos de nodos para hacer el cálculo de la distancia euclidiana no en LUV, sino en el espacio de color L a b *. Ver github.com/zeke/color-namer y github.com/zeke/euclidean-distance
Zeke
1
@user: Me preguntaba: ¿no sería suficiente una muestra del 1% en imágenes grandes para acelerar el cálculo?
jackthehipster
Suena como la mediana geométrica. ¿Quizás esto pueda ayudar? stackoverflow.com/questions/12934213/… y si el punto que estás buscando debe existir en el conjunto original: en.m.wikipedia.org/wiki/Medoid#Algorithms_to_compute_medoids
Lawrence Weru
16

Primero: se puede hacer sin HTML5 Canvas o SVG.
En realidad, alguien logró generar archivos PNG del lado del cliente usando JavaScript , sin lienzo o SVG, usando el esquema de URI de datos .

Segundo: es posible que en realidad no necesite Canvas, SVG o cualquiera de los anteriores.
Si solo necesita procesar imágenes en el lado del cliente, sin modificarlas, todo esto no es necesario.

Puede obtener la dirección de origen de la etiqueta img en la página, realizar una solicitud XHR (lo más probable es que provenga del caché del navegador) y procesarla como un flujo de bytes desde Javascript.
Necesitará una buena comprensión del formato de imagen. (El generador anterior se basa parcialmente en fuentes libpng y podría proporcionar un buen punto de partida).

Andras Vass
fuente
+1 para la solución bytestream. Esta es una buena opción si la única opción es hacerlo en la aplicación cliente.
loretoparisi
11

Diría a través de la etiqueta de lienzo HTML.

Puede encontrar aquí una publicación de @Georg que habla de un pequeño código del desarrollador de 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);

Esto invierte la imagen utilizando los valores R, G y B de cada píxel. Puede almacenar fácilmente los valores RGB, luego redondear las matrices rojo, verde y azul, y finalmente convertirlos de nuevo en un código HEX.

rnaud
fuente
¿Existe alguna posibilidad de que exista una alternativa de IE a la solución de lienzo?
bgreater
@ Ben4Himv: Existe la vista previa de la plataforma IE9 ;-) pero en serio, me temo que no.
Andy E
4

Javascript no tiene acceso a los datos de color de píxeles individuales de una imagen. Al menos, tal vez no hasta html5 ... momento en el que es lógico pensar que podrá dibujar una imagen en un lienzo y luego inspeccionar el lienzo (tal vez, nunca lo he hecho yo mismo).

Joel martinez
fuente
2

Solución todo en uno

Personalmente, combinaría Color Thief junto con esta versión modificada de Name that Color para obtener una gama más que suficiente de resultados de colores dominantes para las imágenes.

Ejemplo:

Considere la siguiente imagen:

ingrese la descripción de la imagen aquí

Puede utilizar el siguiente código para extraer datos de imagen relacionados con el color 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
fuente
1

Se trata de "Cuantificación de color" que tiene varios enfoques como MMCQ (cuantificación de corte mediano modificado) u OQ (cuantificación de octárbol). Diferentes enfoques utilizan K-Means para obtener grupos de colores.

He reunido todo aquí, ya que estaba encontrando una solución para tvOSdonde hay un subconjunto de XHTML, que no tiene ningún <canvas/>elemento:

Genere los colores dominantes para una imagen RGB con XMLHttpRequest

Loretoparisi
fuente
1

Una forma menos precisa pero más rápida de obtener un color promedio de la imagen con dataurisoporte:

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

Como se señaló en otras respuestas, a menudo lo que realmente desea es el color dominante en lugar del color promedio que tiende a ser marrón. Escribí un guión que tiene el color más común y lo publiqué en esta esencia.

dctalbot
fuente
-2

Existe una herramienta online pickimagecolor.com que te ayuda a encontrar el color medio o dominante de la imagen, solo tienes que subir una imagen desde tu ordenador y luego hacer clic en la imagen. Da el color promedio en HEX, RGB y HSV. También encuentra los tonos de color que combinan con ese color para elegir. Lo he usado varias veces.

Shubham Agrawal
fuente