¿Cómo decidir el color de fuente en blanco o negro dependiendo del color de fondo?


222

Quiero mostrar algunas imágenes como este ejemplo.texto alternativo

El color de relleno se decide mediante un campo en la base de datos con el color en hexadecimal (ej .: ClassX -> Color: # 66FFFF). Ahora, quiero mostrar datos sobre un relleno con el color seleccionado (como en la imagen de arriba) pero necesito saber si el color es oscuro o claro, así que sé si las palabras deben estar en blanco o negro. ¿Hay alguna manera? tks


Respuestas:


348

Sobre la base de mi respuesta a una pregunta similar .

Debe dividir el código hexadecimal en 3 partes para obtener las intensidades individuales de rojo, verde y azul. Cada 2 dígitos del código representan un valor en notación hexadecimal (base-16). No entraré en los detalles de la conversión aquí, son fáciles de buscar.

Una vez que tenga las intensidades para los colores individuales, puede determinar la intensidad general del color y elegir el texto correspondiente.

if (red*0.299 + green*0.587 + blue*0.114) > 186 use #000000 else use #ffffff

El umbral de 186 se basa en la teoría, pero se puede ajustar al gusto. Según los comentarios a continuación, un umbral de 150 puede funcionar mejor para usted.


Editar: lo anterior es simple y funciona razonablemente bien, y parece tener buena aceptación aquí en StackOverflow. Sin embargo, uno de los comentarios a continuación muestra que puede dar lugar al incumplimiento de las directrices del W3C en algunas circunstancias. Con esto obtengo una forma modificada que siempre elige el mayor contraste basado en las pautas. Si no necesita cumplir con las reglas del W3C, me quedaría con la fórmula más simple anterior.

La fórmula dada para el contraste en las Recomendaciones del W3C es (L1 + 0.05) / (L2 + 0.05), donde L1está la luminancia del color más claro y L2es la luminancia del más oscuro en una escala de 0.0-1.0. La luminancia del negro es 0.0 y el blanco es 1.0, por lo que sustituir esos valores le permite determinar el que tenga el mayor contraste. Si el contraste para el negro es mayor que el contraste para el blanco, use negro, de lo contrario use blanco. Dada la luminosidad del color que está probando a medida que Lla prueba se convierte en:

if (L + 0.05) / (0.0 + 0.05) > (1.0 + 0.05) / (L + 0.05) use #000000 else use #ffffff

Esto se simplifica algebraicamente para:

if L > sqrt(1.05 * 0.05) - 0.05

O aproximadamente:

if L > 0.179 use #000000 else use #ffffff

Lo único que queda es calcular L. Esa fórmula también se proporciona en las pautas y parece la conversión de sRGB a RGB lineal seguido de la recomendación ITU-R BT.709 para luminancia.

for each c in r,g,b:
    c = c / 255.0
    if c <= 0.03928 then c = c/12.92 else c = ((c+0.055)/1.055) ^ 2.4
L = 0.2126 * r + 0.7152 * g + 0.0722 * b

El umbral de 0.179 no debe cambiarse ya que está vinculado a las pautas del W3C. Si encuentra que los resultados no son de su agrado, intente con la fórmula más simple de arriba.


2
Tks Mark. Probé algunos cambios: calculé el verde rojo y el azul solo por el primer dígito (menos preciso pero es el dígito con más peso) y en lugar de 186 utilizado 9. funciona un poco mejor para mí, especialmente con los verdes.
DJPB

2
Esta fórmula está mal, por mucho. Para tomar un ejemplo, le da al amarillo #D6B508un valor de 171, por lo tanto, un contraste blanco. Sin embargo, el contraste debe ser negro (confirmado aquí: webaim.org/resources/contrastchecker )
McGarnagle

1
@MarkRansom sí, a mis ojos el negro se ve mucho mejor con ese amarillo. Como estoy trabajando con un conjunto limitado de colores, pude cambiar el límite de 186 a un valor más bajo.
McGarnagle

3
Aquí hay otra demostración que compara las dos fórmulas dadas en esta respuesta. Usando el selector de color (en Firefox o Chrome recientes) puede verificar el contraste con cualquier color.
Chetstone

3
Después de jugar un poco con la demostración de @ chetstone, creo que la fórmula simple funciona mejor para mí, excepto que no estoy de acuerdo con la recomendación del umbral. Basado en los resultados del verde puro, intenté establecer el umbral en 149 y eso funciona mucho mejor en mi opinión. Hice un violín realmente tonto para demostrar esto ; puede intentar cambiar el umbral en la parte superior para ver la sugerencia original.
Miral

30

No tomo ningún crédito por este código, ya que no es mío, pero lo dejo aquí para que otros lo encuentren rápidamente en el futuro:

Según la respuesta de Mark Ransoms, aquí hay un fragmento de código para la versión simple:

function pickTextColorBasedOnBgColorSimple(bgColor, lightColor, darkColor) {
  var color = (bgColor.charAt(0) === '#') ? bgColor.substring(1, 7) : bgColor;
  var r = parseInt(color.substring(0, 2), 16); // hexToR
  var g = parseInt(color.substring(2, 4), 16); // hexToG
  var b = parseInt(color.substring(4, 6), 16); // hexToB
  return (((r * 0.299) + (g * 0.587) + (b * 0.114)) > 186) ?
    darkColor : lightColor;
}

y aquí está el fragmento de código para la versión avanzada:

function pickTextColorBasedOnBgColorAdvanced(bgColor, lightColor, darkColor) {
  var color = (bgColor.charAt(0) === '#') ? bgColor.substring(1, 7) : bgColor;
  var r = parseInt(color.substring(0, 2), 16); // hexToR
  var g = parseInt(color.substring(2, 4), 16); // hexToG
  var b = parseInt(color.substring(4, 6), 16); // hexToB
  var uicolors = [r / 255, g / 255, b / 255];
  var c = uicolors.map((col) => {
    if (col <= 0.03928) {
      return col / 12.92;
    }
    return Math.pow((col + 0.055) / 1.055, 2.4);
  });
  var L = (0.2126 * c[0]) + (0.7152 * c[1]) + (0.0722 * c[2]);
  return (L > 0.179) ? darkColor : lightColor;
}

Para usarlos solo llame:

var color = '#EEACAE' // this can be any color
pickTextColorBasedOnBgColorSimple(color, '#FFFFFF', '#000000');

Además, gracias Alxy chetstone.


1
Utilicé la función simple y la eliminé un poco más: eliminé los 2 últimos parámetros y isDark(bgColor)'color': isDark(color)?'white':'black'
cambié el

1
Funcionó como un encanto para mí. ¡Muchas gracias! Lo hice en php aunque simplemente convirtió la sintaxis y las funciones.
CezarBastos

19

¿Qué tal esto (código JavaScript)?

/**
 * Get color (black/white) depending on bgColor so it would be clearly seen.
 * @param bgColor
 * @returns {string}
 */
getColorByBgColor(bgColor) {
    if (!bgColor) { return ''; }
    return (parseInt(bgColor.replace('#', ''), 16) > 0xffffff / 2) ? '#000' : '#fff';
}

1
Esto funciona la mayor parte del tiempo, pero hay casos como i.imgur.com/3pOUDe5.jpg que se ven raros, el color de fondo es en realidad rgb (6, 247, 241);
madprops

Aprecio esta es una manera relativamente barata de realizar un cálculo de contraste en los casos en que no lo ha convertido el color a los valores RGB (no es que eso es demasiado difícil, simplemente pasos adicionales de matemáticas)
frumbert

12

Además de las soluciones aritméticas, también es posible utilizar una red neuronal AI. La ventaja es que puede adaptarlo a su propio gusto y necesidades (es decir, el texto blanquecino en rojos saturados brillantes se ve bien y es tan legible como el negro).

Aquí hay una demostración de Javascript ordenada que ilustra el concepto. También puede generar su propia fórmula JS directamente en la demostración.

https://harthur.github.io/brain/

A continuación hay algunos cuadros que me ayudaron a entender el problema . En el primer gráfico, la luminosidad es constante 128, mientras que el tono y la saturación varían. En el segundo gráfico, la saturación es una constante 255, mientras que el tono y la luminosidad varían.

En el primer gráfico, la ligereza es constante 128, mientras que el tono y la saturación varían:

La saturación es una constante 255, mientras que el tono y la ligereza varían:


8

Aquí está mi solución en Java para Android:

// Put this method in whichever class you deem appropriate
// static or non-static, up to you.
public static int getContrastColor(int colorIntValue) {
    int red = Color.red(colorIntValue);
    int green = Color.green(colorIntValue);
    int blue = Color.blue(colorIntValue);
    double lum = (((0.299 * red) + ((0.587 * green) + (0.114 * blue))));
    return lum > 186 ? 0xFF000000 : 0xFFFFFFFF;
}

// Usage
// If Color is represented as HEX code:
String colorHex = "#484588";
int color = Color.parseColor(colorHex);

// Or if color is Integer:
int color = 0xFF484588;

// Get White (0xFFFFFFFF) or Black (0xFF000000)
int contrastColor = WhateverClass.getContrastColor(color);

2
¿Es realmente 'perfecto'? Pruebe el fondo verde puro, # 00FF00.
Andreas Rejbrand

Es cierto que esto no se prueba para todos los colores ... pero ¿quién usaría un fondo verde puro para cualquier cosa que no haya sido diseñada para molestar a los usuarios?
mwieczorek

@mwieczorek las personas que confían en contenido generado por el usuario o colores seleccionados al azar lo hacen.
Marc Plano-Lesay

4

Basado en la respuesta de @MarkRansom, creé un script PHP que puedes encontrar aquí:

function calcC($c) {
    if ($c <= 0.03928) {
        return $c / 12.92;
    }
    else {
        return pow(($c + 0.055) / 1.055, 2.4);
    }
}

function cutHex($h) {
    return ($h[0] == "#") ? substr($h, 1, 7) : $h;
}

function hexToR($h) {
    return hexdec(substr(cutHex($h), 0, 2));
}

function hexToG($h) {
    return hexdec(substr(cutHex($h), 2, 2)); // Edited
}

function hexToB($h) {
    return hexdec(substr(cutHex($h), 4, 2)); // Edited
}

function computeTextColor($color) {
    $r = hexToR($color);
    $g = hexToG($color);
    $b = hexToB($color);
    $uicolors = [$r / 255, $g / 255, $b / 255];


    $c = array_map("calcC", $uicolors);

    $l = 0.2126 * $c[0] + 0.7152 * $c[1] + 0.0722 * $c[2];
    return ($l > 0.179) ? '#000000' : '#ffffff';
}

3

Esta es una versión rápida de la respuesta de Mark Ransom como una extensión de UIColor

extension UIColor {

// Get the rgba components in CGFloat
var rgba: (red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat) {
    var red: CGFloat = 0, green: CGFloat = 0, blue: CGFloat = 0, alpha: CGFloat = 0

    getRed(&red, green: &green, blue: &blue, alpha: &alpha)

    return (red, green, blue, alpha)
}

/// Return the better contrasting color, white or black
func contrastColor() -> UIColor {
    let rgbArray = [rgba.red, rgba.green, rgba.blue]

    let luminanceArray = rgbArray.map({ value -> (CGFloat) in
        if value < 0.03928 {
            return (value / 12.92)
        } else {
            return (pow( (value + 0.55) / 1.055, 2.4) )
        }
    })

    let luminance = 0.2126 * luminanceArray[0] +
        0.7152 * luminanceArray[1] +
        0.0722 * luminanceArray[2]

    return luminance > 0.179 ? UIColor.black : UIColor.white
} }

2

Este es solo un ejemplo que cambiará el color de una marca de verificación SVG al hacer clic en un elemento. Establecerá el color de la marca de verificación en blanco o negro según el color de fondo del elemento seleccionado.

checkmarkColor: function(el) {
    var self = el;
    var contrast = function checkContrast(rgb) {
        // @TODO check for HEX value

        // Get RGB value between parenthesis, and remove any whitespace
        rgb = rgb.split(/\(([^)]+)\)/)[1].replace(/ /g, '');

        // map RGB values to variables
        var r = parseInt(rgb.split(',')[0], 10),
            g = parseInt(rgb.split(',')[1], 10),
            b = parseInt(rgb.split(',')[2], 10),
            a;

        // if RGBA, map alpha to variable (not currently in use)
        if (rgb.split(',')[3] !== null) {
            a = parseInt(rgb.split(',')[3], 10);
        }

        // calculate contrast of color (standard grayscale algorithmic formula)
        var contrast = (Math.round(r * 299) + Math.round(g * 587) + Math.round(b * 114)) / 1000;

        return (contrast >= 128) ? 'black' : 'white';
    };

    $('#steps .step.color .color-item .icon-ui-checkmark-shadow svg').css({
        'fill': contrast($(self).css('background-color'))
    });
}

onClickExtColor: function(evt) {
    var self = this;

    self.checkmarkColor(evt.currentTarget);
}

https://gist.github.com/dcondrey/183971f17808e9277572


2

Yo uso esta función JavaScript para convertir rgb/ rgbaa 'white'o 'black'.

function getTextColor(rgba) {
    rgba = rgba.match(/\d+/g);
    if ((rgba[0] * 0.299) + (rgba[1] * 0.587) + (rgba[2] * 0.114) > 186) {
        return 'black';
    } else {
        return 'white';
    }
}

Puede entrar en cualquiera de estos formatos y es la salida 'black'o'white'

  • rgb(255,255,255)
  • rgba(255,255,255,0.1)
  • color:rgba(255,255,255,0.1)
  • 255,255,255,0.1

Ahora intente esto con un fondo verde puro: # 00FF00.
Andreas Rejbrand

¡Gracias por esto! Traducido a Swift y lo usé en mi aplicación ios!
Lucas P.

2

La respuesta detallada de Mark funciona muy bien. Aquí hay una implementación en javascript:

function lum(rgb) {
    var lrgb = [];
    rgb.forEach(function(c) {
        c = c / 255.0;
        if (c <= 0.03928) {
            c = c / 12.92;
        } else {
            c = Math.pow((c + 0.055) / 1.055, 2.4);
        }
        lrgb.push(c);
    });
    var lum = 0.2126 * lrgb[0] + 0.7152 * lrgb[1] + 0.0722 * lrgb[2];
    return (lum > 0.179) ? '#000000' : '#ffffff';
}

Entonces puede llamar a esta función lum([111, 22, 255])para obtener blanco o negro.


1

Nunca he hecho algo como esto, pero ¿qué hay de escribir una función para verificar los valores de cada uno de los colores contra el color medio de Hex 7F (FF / 2)? Si dos de los tres colores son mayores que 7F, entonces está trabajando con un color más oscuro.


1

Basado en diferentes entradas del enlace Haga que el color de primer plano sea blanco o negro dependiendo del fondo y este hilo, hice una clase de extensión para Color que le brinda los colores de contraste requeridos.

El código va de la siguiente manera:

 public static class ColorExtension
{       
    public static int PerceivedBrightness(this Color c)
    {
        return (int)Math.Sqrt(
        c.R * c.R * .299 +
        c.G * c.G * .587 +
        c.B * c.B * .114);
    }
    public static Color ContrastColor(this Color iColor, Color darkColor,Color lightColor)
    {
        //  Counting the perceptive luminance (aka luma) - human eye favors green color... 
        double luma = (iColor.PerceivedBrightness() / 255);

        // Return black for bright colors, white for dark colors
        return luma > 0.5 ? darkColor : lightColor;
    }
    public static Color ContrastColor(this Color iColor) => iColor.ContrastColor(Color.Black);
    public static Color ContrastColor(this Color iColor, Color darkColor) => iColor.ContrastColor(darkColor, Color.White);
    // Converts a given Color to gray
    public static Color ToGray(this Color input)
    {
        int g = (int)(input.R * .299) + (int)(input.G * .587) + (int)(input.B * .114);
        return Color.FromArgb(input.A, g, g, g);
    }
}

1

Si como yo estabas buscando una versión RGBA que tenga en cuenta alfa, aquí hay una que funciona muy bien para tener un alto contraste.

function getContrastColor(R, G, B, A) {
  const brightness = R * 0.299 + G * 0.587 + B * 0.114 + (1 - A) * 255;

  return brightness > 186 ? "#000000" : "#FFFFFF";
}

1

Esta es una versión R de la respuesta de Mark Ransom, utilizando solo la base R.

hex_bw <- function(hex_code) {

  myrgb <- as.integer(col2rgb(hex_code))

  rgb_conv <- lapply(myrgb, function(x) {
    i <- x / 255
    if (i <= 0.03928) {
      i <- i / 12.92
    } else {
      i <- ((i + 0.055) / 1.055) ^ 2.4
    }
    return(i)
  })

 rgb_calc <- (0.2126*rgb_conv[[1]]) + (0.7152*rgb_conv[[2]]) + (0.0722*rgb_conv[[3]])

 if (rgb_calc > 0.179) return("#000000") else return("#ffffff")

}

> hex_bw("#8FBC8F")
[1] "#000000"
> hex_bw("#7fa5e3")
[1] "#000000"
> hex_bw("#0054de")
[1] "#ffffff"
> hex_bw("#2064d4")
[1] "#ffffff"
> hex_bw("#5387db")
[1] "#000000"

0

@SoBiT, estaba viendo tu respuesta, que se ve bien, pero hay un pequeño error. Su función hexToG y hextoB necesitan una edición menor. El último número en substr es la longitud de la cadena, por lo que en este caso debería ser "2", en lugar de 4 o 6.

function hexToR($h) {
    return hexdec(substr(cutHex($h), 0, 2));
}
function hexToG($h) {
    return hexdec(substr(cutHex($h), 2, 2));
}
function hexToB($h) {
    return hexdec(substr(cutHex($h), 4, 2));
}

0

MENOS tiene una buena contrast()función que funcionó muy bien para mí, consulte http://lesscss.org/functions/#color-operations-contrast

"Elija cuál de los dos colores proporciona el mayor contraste con el otro. Esto es útil para garantizar que un color sea legible en un fondo, lo que también es útil para el cumplimiento de accesibilidad. Esta función funciona de la misma manera que la función de contraste en Compass for SASS. De acuerdo con WCAG 2.0, los colores se comparan utilizando su valor de luma con corrección gamma, no su luminosidad ".

Ejemplo:

p {
    a: contrast(#bbbbbb);
    b: contrast(#222222, #101010);
    c: contrast(#222222, #101010, #dddddd);
    d: contrast(hsl(90, 100%, 50%), #000000, #ffffff, 30%);
    e: contrast(hsl(90, 100%, 50%), #000000, #ffffff, 80%);
}

Salida:

p {
    a: #000000 // black
    b: #ffffff // white
    c: #dddddd
    d: #000000 // black
    e: #ffffff // white
}

0

De hexadecimal a blanco o negro:

function hexToRgb(hex) {
  var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
  return result
    ? [
        parseInt(result[1], 16),
        parseInt(result[2], 16),
        parseInt(result[3], 16)
      ]
    : [0, 0, 0];
}

function lum(hex) {
  var rgb = hexToRgb(hex)
  var lrgb = [];
  rgb.forEach(function(c) {
    c = c / 255.0;
    if (c <= 0.03928) {
      c = c / 12.92;
    } else {
      c = Math.pow((c + 0.055) / 1.055, 2.4);
    }
    lrgb.push(c);
  });
  var lum = 0.2126 * lrgb[0] + 0.7152 * lrgb[1] + 0.0722 * lrgb[2];
  return lum > 0.179 ? "#000000" : "#ffffff";
}

0

Código de versión de Objective-c para iOS basado en la respuesta de Mark:

- (UIColor *)contrastForegroundColor {
CGFloat red = 0, green = 0, blue = 0, alpha = 0;
[self getRed:&red green:&green blue:&blue alpha:&alpha];
NSArray<NSNumber *> *rgbArray = @[@(red), @(green), @(blue)];
NSMutableArray<NSNumber *> *parsedRGBArray = [NSMutableArray arrayWithCapacity:rgbArray.count];
for (NSNumber *item in rgbArray) {
    if (item.doubleValue <= 0.03928) {
        [parsedRGBArray addObject:@(item.doubleValue / 12.92)];
    } else {
        double newValue = pow((item.doubleValue + 0.055) / 1.055, 2.4);
        [parsedRGBArray addObject:@(newValue)];
    }
}

double luminance = 0.2126 * parsedRGBArray[0].doubleValue + 0.7152 * parsedRGBArray[1].doubleValue + 0.0722 * parsedRGBArray[2].doubleValue;

return luminance > 0.179 ? UIColor.blackColor : UIColor.whiteColor;
}

0

¿Qué pasa con las pruebas con todos los colores de 24 bits?

Tenga en cuenta que el método YIQ devolverá una relación de contraste mínima de 1.9: 1, que no pasa las pruebas AA y AAA WCAG2.0, suponiendo un umbral de 128.

Para el método W3C, devolverá una relación de contraste mínima de 4.58: 1, que pasa las pruebas AA y AAA para texto grande, y la prueba AA para texto pequeño, no pasará la prueba AAA para texto pequeño para todos los colores.


0

Aquí está mi propio método que he estado usando y con el que no he tenido problemas hasta ahora 😄

const hexCode = value.charAt(0) === '#' 
                  ? value.substr(1, 6)
                  : value;

const hexR = parseInt(hexCode.substr(0, 2), 16);
const hexG = parseInt(hexCode.substr(2, 2), 16);
const hexB = parseInt(hexCode.substr(4, 2), 16);
// Gets the average value of the colors
const contrastRatio = (hexR + hexG + hexB) / (255 * 3);

contrastRatio >= 0.5
  ? 'black'
  : 'white';


0

Aquí está mi código para Java Swing basado en la sorprendente respuesta de Mark:

public static Color getColorBasedOnBackground(Color background, Color darkColor, Color lightColor) {
    // Calculate foreground color based on background (based on https://stackoverflow.com/a/3943023/)
    Color color;
    double[] cL = new double[3];
    double[] colorRGB = new double[] {background.getRed(), background.getGreen(), background.getBlue()};

    for (int i = 0; i < colorRGB.length; i++)
        cL[i] = (colorRGB[i] / 255.0 <= 0.03928) ? colorRGB[i] / 255.0 / 12.92 :
                Math.pow(((colorRGB[i] / 255.0 + 0.055) / 1.055), 2.4);

    double L = 0.2126 * cL[0] + 0.7152 * cL[1] + 0.0722 * cL[2];
    color = (L > Math.sqrt(1.05 * 0.05) - 0.05) ? darkColor : lightColor;

    return color;
}
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.