¿Cómo comprobar si el color hexadecimal es "demasiado negro"?

111

Estoy tratando de evaluar la oscuridad de un color elegido por un selector de color para ver si es "demasiado negro" y, si es así, establecerlo en blanco. Pensé que podría usar los primeros caracteres del valor hexadecimal para lograrlo. Está funcionando, pero también está cambiando algunos colores legítimamente "claros".

Tengo un código haciendo esto:

        if (lightcolor.substring(0,3) == "#00"|| lightcolor.substring(0,3) == "#010"){
            lightcolor="#FFFFFF";
            color=lightcolor;
        }

¿Debe haber una forma más eficiente con las matemáticas hexadecimales para saber que un color ha ido más allá de cierto nivel de oscuridad? Como si color claro + "algún valor hexadecimal" <= "algún valor hexadecimal", entonces póngalo en blanco.

He agregado tinyColor, que podría ser útil para esto, pero no estoy seguro.

Dshiz
fuente
1
¿Ha intentado utilizar un selector de color y comprobar los valores? Noté que cuando R, G y B tienen menos de ~ 70, oscurece. Puede que esta no sea la forma correcta, pero es una.
Rick Kuipers
1
Como ya está usando tinyColor, transforme el color a HSL y observe el componente L. 1 = blanco, 0 = negro
Andreas
4
@Andreas La ligereza de HSL no tiene en cuenta la percepción humana. Un valor L de 0.5 tendrá un brillo percibido diferente para diferentes tonos.
Alnitak
1
@Alnitak Tienes razón, pero la descripción del TO no es tan precisa. Entonces, cualquier valor por debajo de 3/8 podría haber sido lo suficientemente oscuro para su propósito.
Andreas
1
@ Andreas, eso depende: si observa los valores de luminancia de la UIT en mi respuesta, verá que el azul se percibe como solo una décima parte del brillo del verde.
Alnitak

Respuestas:

226

Debe extraer los tres componentes RGB individualmente y luego usar una fórmula estándar para convertir los valores RGB resultantes en su brillo percibido.

Asumiendo un color de seis caracteres:

var c = c.substring(1);      // strip #
var rgb = parseInt(c, 16);   // convert rrggbb to decimal
var r = (rgb >> 16) & 0xff;  // extract red
var g = (rgb >>  8) & 0xff;  // extract green
var b = (rgb >>  0) & 0xff;  // extract blue

var luma = 0.2126 * r + 0.7152 * g + 0.0722 * b; // per ITU-R BT.709

if (luma < 40) {
    // pick a different colour
}

EDITAR

Desde mayo de 2014 tinycolorahora tiene una getBrightness()función, aunque utiliza los factores de ponderación CCIR601 en lugar de los de ITU-R anteriores.

EDITAR

El rango de valores de luma resultante es 0..255, donde 0 es el más oscuro y 255 es el más claro. Los valores superiores a 128 se consideran ligeros por tinycolor. (copiado descaradamente de los comentarios de @ pau.moreno y @Alnitak)

Alnitak
fuente
11
No he visto una buena manipulación de bits en javascript en un tiempo. cosas interesantes. en.wikipedia.org/wiki/Rec._709#Luma_coefficients
jbabey
2
Buen código, pero después de probar sugiero var luma = (r + g + b) / 3; if (luma <128) {// será más útil. }
Terry Lin
2
@TerryLin ¿Por qué? Los coeficientes dados son los valores estándar de ITU que permiten el hecho de que el verde se percibe con más brillo que el rojo (y luego el azul).
Alnitak
1
El lumarango de valores resultante es 0..255, donde 0es el más oscuro y 255el más claro (los tres coeficientes suman uno).
pau.moreno
1
@gabssnake solo desde mayo de 2014, y el isDark()umbral está codificado en 128
Alnitak
21

La biblioteca TinyColor (ya la ha mencionado) proporciona varias funciones para inspeccionar y manipular colores, entre ellas:

skalee
fuente
15

Encontré esta función PHP de WooCommerce Wordpress ( wc_hex_is_light ) y la convertí a JavaScript. ¡Funciona bien!

function wc_hex_is_light(color) {
    const hex = color.replace('#', '');
    const c_r = parseInt(hex.substr(0, 2), 16);
    const c_g = parseInt(hex.substr(2, 2), 16);
    const c_b = parseInt(hex.substr(4, 2), 16);
    const brightness = ((c_r * 299) + (c_g * 587) + (c_b * 114)) / 1000;
    return brightness > 155;
}
Sergio Cabral
fuente
1
¡Genial, gracias! Lo probé con varios colores, la detección fue correcta con todos ellos :)
David Dal Busco
colorIsDarkOrLight (color) {var hex = color.replace ("#", ""); var c_r, c_g, c_b, brillo = ""; if (hex.length == 3) {c_r = parseInt (hex.substr (0, 2), 16); c_g = parseInt (hex.substr (1, 2), 16); c_b = parseInt (hex.substr (2, 2), 16); brillo = (c_r * 299 + c_g * 587 + c_b * 114) / 1000; } más {c_r = parseInt (hex.substr (0, 2), 16); c_g = parseInt (hex.substr (2, 2), 16); c_b = parseInt (hex.substr (4, 2), 16); } volver brillo> 155; },
Pedro Henrique
Usar con hex 3 caracteres y 6
Pedro Henrique
6

Puede calcular la luminancia :

La luminancia es, por tanto, un indicador del brillo de la superficie.

Así que es genial elegir si el texto debe ser blanco o negro.

var getRGB = function(b){
    var a;
    if(b&&b.constructor==Array&&b.length==3)return b;
    if(a=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(b))return[parseInt(a[1]),parseInt(a[2]),parseInt(a[3])];
    if(a=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(b))return[parseFloat(a[1])*2.55,parseFloat(a[2])*2.55,parseFloat(a[3])*2.55];
    if(a=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(b))return[parseInt(a[1],16),parseInt(a[2],16),parseInt(a[3],
16)];
    if(a=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(b))return[parseInt(a[1]+a[1],16),parseInt(a[2]+a[2],16),parseInt(a[3]+a[3],16)];
    return (typeof (colors) != "undefined")?colors[jQuery.trim(b).toLowerCase()]:null
};

var luminance_get = function(color) {
    var rgb = getRGB(color);
    if (!rgb) return null;
        return 0.2126 * rgb[0] + 0.7152 * rgb[1] + 0.0722 * rgb[2];
}

El método anterior le permite pasar el color en diferentes formatos, pero el algoritmo es básicamente luminance_get.

Cuando lo usé, estaba configurando el color en negro si la luminancia era mayor que 180, blanco en caso contrario.

Robin
fuente
5

Aquí hay una distinción importante entre luminancia y brillo. La luminancia, al final del día, es una medida de cuánta energía viaja a través de un área determinada e ignora por completo cómo nuestros sistemas de percepción perciben esa energía. El brillo, por otro lado, es una medida de cómo percibimos esa energía y tiene en cuenta la relación entre la luminancia y nuestro sistema de percepción. (Como punto de confusión, hay un término llamado luminancia relativa, que parece ser usado como sinónimo de términos de brillo. Me sorprendió mucho).

Para ser precisos, busca "brillo" o "valor" o "luminancia relativa", como han sugerido otros. Puede calcular esto de varias maneras diferentes (¡eso es ser humano!) Http://en.wikipedia.org/wiki/HSL_and_HSV#Lightness

  1. Tome el máximo de R, G y B.
  2. Tome el promedio del máximo y el mínimo de R, G y B.
  3. Toma el promedio de los tres.
  4. Utilice un promedio ponderado como otros han sugerido aquí.
David Nguyen
fuente
AFAIK, solo el cálculo de luma descrito en la página de Wikipedia es un modelo basado en la percepción.
Alnitak
2
Es bueno señalar la distinción entre la energía de la luz física y el brillo percibido, pero creo que tienes las cosas bastante confusas. La sección del artículo de Wikipedia al que vinculó tiene un cuarto punto, que dice "Una alternativa más perceptualmente relevante es usar luma, Y ′, como una dimensión de ligereza" (el énfasis es mío) y luego procede a dar la fórmula presentada en Alnitak y las respuestas de Robin. En otras palabras, el método que ha omitido y recomendado es el que mejor se adapta a la percepción humana.
John Y
@JohnY sí, eso es lo que estaba tratando de decir: dejó fuera el único que realmente coincide con el resto de su respuesta.
Alnitak
Sí, resulta que yo era el único confundido aquí. Estoy de acuerdo con eso :) Solo quería dejar claro que hay una diferencia entre la energía y la percepción. Actualizaré mi respuesta en consecuencia.
David Nguyen
4

Este trabajo con hexadecimal, por ejemplo, #fefefe

function isTooDark(hexcolor){
    var r = parseInt(hexcolor.substr(1,2),16);
    var g = parseInt(hexcolor.substr(3,2),16);
    var b = parseInt(hexcolor.substr(4,2),16);
    var yiq = ((r*299)+(g*587)+(b*114))/1000;
    // Return new color if to dark, else return the original
    return (yiq < 40) ? '#2980b9' : hexcolor;
}

Puedes cambiarlo por volver trueo falsepor cambio

return (yiq < 40) ? '#2980b9' : hexcolor;

a

return (yiq < 40);
TheCrazyProfessor
fuente
@Vivek, eso se debe a que necesita usar valores hexadecimales completos (# 000000 y #ffffff)
TheCrazyProfessor
2

Una posible solución sería convertir su color de RGB a HSB . HSB significa tono, saturación y brillo (también conocido como HSV, donde V es el valor). Entonces solo tiene un parámetro para verificar: brillo.

Ohad
fuente
1

Me doy cuenta de que esta conversación tiene algunos años, pero sigue siendo relevante. Quería agregar que mi equipo estaba teniendo el mismo problema en Java (SWT) y encontré que esto era un poco más preciso:

private Color getFontColor(RGB bgColor) {
    Color COLOR_BLACK = new Color(Display.getDefault(), 0, 0, 0);
    Color COLOR_WHITE = new Color(Display.getDefault(), 255, 255, 255);

    double luminance = Math.sqrt(0.241 
       * Math.pow(bgColor.red, 2) + 0.691 * Math.pow(bgColor.green, 2) +  0.068 
       * Math.pow(bgColor.blue, 2));
    if (luminance >= 130) {
        return COLOR_BLACK;
    } else {
        return COLOR_WHITE;
    }
}
Chris Clark
fuente