Determinar el color de fuente basado en el color de fondo

248

Dado un sistema (un sitio web, por ejemplo) que permite al usuario personalizar el color de fondo para alguna sección pero no el color de fuente (para mantener el número de opciones al mínimo), ¿hay alguna manera de determinar mediante programación si una "luz" o " ¿Es necesario el color de fuente oscuro?

Estoy seguro de que hay algún algoritmo, pero no sé lo suficiente sobre colores, luminosidad, etc. para resolverlo por mi cuenta.

Joseph Daigle
fuente

Respuestas:

447

Encontré un problema similar. Tenía que encontrar un buen método para seleccionar el color de fuente contrastante para mostrar etiquetas de texto en escalas de colores / mapas de calor. Tenía que ser un método universal y el color generado tenía que ser "atractivo", lo que significa que la generación de color complementario simple no era una buena solución; a veces generaba colores extraños e intensos que eran difíciles de ver y leer.

Después de largas horas de pruebas y tratando de resolver este problema, descubrí que la mejor solución es seleccionar la fuente blanca para colores "oscuros" y la fuente negra para colores "brillantes".

Aquí hay un ejemplo de función que estoy usando en C #:

Color ContrastColor(Color color)
{
    int d = 0;

    // Counting the perceptive luminance - human eye favors green color... 
    double luminance = ( 0.299 * color.R + 0.587 * color.G + 0.114 * color.B)/255;

    if (luminance > 0.5)
       d = 0; // bright colors - black font
    else
       d = 255; // dark colors - white font

    return  Color.FromArgb(d, d, d);
}

Esto se probó para muchas escalas de colores diferentes (arco iris, escala de grises, calor, hielo y muchos otros) y es el único método "universal" que descubrí.

Editar
Cambió la fórmula de contar aa "luminancia perceptiva", ¡realmente se ve mejor! Ya implementado en mi software, se ve muy bien.

Edit 2 @WebSeed proporcionó un gran ejemplo de trabajo de este algoritmo: http://codepen.io/WebSeed/full/pvgqEq/

Gacek
fuente
14
Probablemente no sea importante, pero es posible que desee una mejor función para calcular el brillo stackoverflow.com/questions/596216/…
Josh Lee
1
Parece que será perfecto.
Joseph Daigle
Exactamente lo que necesitaba hoy.
Chris W
¿De dónde provienen sus perceptivas ponderaciones de luminancia?
Mat
De esta respuesta: stackoverflow.com/questions/596216/…
Gacek
23

En caso de que a alguien le gustaría una versión más corta, tal vez más fácil de entender, de la respuesta de GaceK :

public Color ContrastColor(Color iColor)
{
   // Calculate the perceptive luminance (aka luma) - human eye favors green color... 
   double luma = ((0.299 * iColor.R) + (0.587 * iColor.G) + (0.114 * iColor.B)) / 255;

   // Return black for bright colors, white for dark colors
   return luma > 0.5 ? Color.Black : Color.White;
}

Nota: eliminé la inversión del valor de luma (para que los colores brillantes tengan un valor más alto, lo que me parece más natural y también es el método de cálculo 'predeterminado'.

Usé las mismas constantes que GaceK desde aquí, ya que funcionaron muy bien para mí.

(También puede implementar esto como un Método de Extensión usando la siguiente firma:

public static Color ContrastColor(this Color iColor)

Luego puede llamarlo a través de foregroundColor = background.ContrastColor()).

Marcus Mangelsdorf
fuente
11

Gracias @Gacek . Aquí hay una versión para Android:

@ColorInt
public static int getContrastColor(@ColorInt int color) {
    // Counting the perceptive luminance - human eye favors green color...
    double a = 1 - (0.299 * Color.red(color) + 0.587 * Color.green(color) + 0.114 * Color.blue(color)) / 255;

    int d;
    if (a < 0.5) {
        d = 0; // bright colors - black font
    } else {
        d = 255; // dark colors - white font
    }

    return Color.rgb(d, d, d);
}

Y una versión mejorada (más corta):

@ColorInt
public static int getContrastColor(@ColorInt int color) {
    // Counting the perceptive luminance - human eye favors green color...
    double a = 1 - (0.299 * Color.red(color) + 0.587 * Color.green(color) + 0.114 * Color.blue(color)) / 255;
    return a < 0.5 ? Color.BLACK : Color.WHITE;
}
Thomas Vos
fuente
Sería aún más corto y fácil de leer si aplicaras los cambios de @Marcus Mangelsdorf (deshazte de la 1 - ...parte y cámbiale el nombre aaluminance
Ridcully el
Usando el primero, también podría capturar alfa.
Brill Pappin
9

Mi implementación rápida de la respuesta de Gacek:

func contrastColor(color: UIColor) -> UIColor {
    var d = CGFloat(0)

    var r = CGFloat(0)
    var g = CGFloat(0)
    var b = CGFloat(0)
    var a = CGFloat(0)

    color.getRed(&r, green: &g, blue: &b, alpha: &a)

    // Counting the perceptive luminance - human eye favors green color...
    let luminance = 1 - ((0.299 * r) + (0.587 * g) + (0.114 * b))

    if luminance < 0.5 {
        d = CGFloat(0) // bright colors - black font
    } else {
        d = CGFloat(1) // dark colors - white font
    }

    return UIColor( red: d, green: d, blue: d, alpha: a)
}
Vito Royeca
fuente
En rápido, dado que r / g / b son CGFloats, no necesita el "/ 255" para calcular la luminancia: deje luminancia = 1 - ((0.299 * r) + (0.587 * g) + (0.114 * b))
SuperDuperTango
8

Javascript [ES2015]

const hexToLuma = (colour) => {
    const hex   = colour.replace(/#/, '');
    const r     = parseInt(hex.substr(0, 2), 16);
    const g     = parseInt(hex.substr(2, 2), 16);
    const b     = parseInt(hex.substr(4, 2), 16);

    return [
        0.299 * r,
        0.587 * g,
        0.114 * b
    ].reduce((a, b) => a + b) / 255;
};
matfin
fuente
6

Gracias por este post

Para quien esté interesado, aquí hay un ejemplo de esa función en Delphi:

function GetContrastColor(ABGColor: TColor): TColor;
var
  ADouble: Double;
  R, G, B: Byte;
begin
  if ABGColor <= 0 then
  begin
    Result := clWhite;
    Exit; // *** EXIT RIGHT HERE ***
  end;

  if ABGColor = clWhite then
  begin
    Result := clBlack;
    Exit; // *** EXIT RIGHT HERE ***
  end;

  // Get RGB from Color
  R := GetRValue(ABGColor);
  G := GetGValue(ABGColor);
  B := GetBValue(ABGColor);

  // Counting the perceptive luminance - human eye favors green color...
  ADouble := 1 - (0.299 * R + 0.587 * G + 0.114 * B) / 255;

  if (ADouble < 0.5) then
    Result := clBlack  // bright colors - black font
  else
    Result := clWhite;  // dark colors - white font
end;
Richard D.
fuente
Hay un pequeño error en el 4to de la última línea. Resultado: = clBlack no debería tener un punto y coma después;
Andy k
5

Esta es una respuesta muy útil. ¡Gracias por eso!

Me gustaría compartir una versión de SCSS:

@function is-color-light( $color ) {

  // Get the components of the specified color
  $red: red( $color );
  $green: green( $color );
  $blue: blue( $color );

  // Compute the perceptive luminance, keeping
  // in mind that the human eye favors green.
  $l: 1 - ( 0.299 * $red + 0.587 * $green + 0.114 * $blue ) / 255;
  @return ( $l < 0.5 );

}

Ahora descubriendo cómo usar el algoritmo para crear automáticamente colores de desplazamiento para los enlaces del menú. Los encabezados ligeros se oscurecen y viceversa.

ricotheque
fuente
3

Implementación de aleteo

Color contrastColor(Color color) {
  if (color == Colors.transparent || color.alpha < 50) {
    return Colors.black;
  }
  double luminance = (0.299 * color.red + 0.587 * color.green + 0.114 * color.blue) / 255;
  return luminance > 0.5 ? Colors.black : Colors.white;
}
Mamnarock
fuente
Todo lo que hice fue prefijo con 'static'. ¡Gracias!
Pete Alvin
2

Python feo si no tienes ganas de escribirlo :)

'''
Input a string without hash sign of RGB hex digits to compute
complementary contrasting color such as for fonts
'''
def contrasting_text_color(hex_str):
    (r, g, b) = (hex_str[:2], hex_str[2:4], hex_str[4:])
    return '000' if 1 - (int(r, 16) * 0.299 + int(g, 16) * 0.587 + int(b, 16) * 0.114) / 255 < 0.5 else 'fff'
Joseph Coco
fuente
2

Tuve el mismo problema pero tuve que desarrollarlo en PHP . Utilicé la solución de @ Garek y también utilicé esta respuesta: Convertir color hexadecimal a valores RGB en PHP para convertir el código de color HEX a RGB.

Así que lo estoy compartiendo.

Quería usar esta función con el color de fondo HEX dado, pero no siempre comenzando desde '#'.

//So it can be used like this way:
$color = calculateColor('#804040');
echo $color;

//or even this way:
$color = calculateColor('D79C44');
echo '<br/>'.$color;

function calculateColor($bgColor){
    //ensure that the color code will not have # in the beginning
    $bgColor = str_replace('#','',$bgColor);
    //now just add it
    $hex = '#'.$bgColor;
    list($r, $g, $b) = sscanf($hex, "#%02x%02x%02x");
    $color = 1 - ( 0.299 * $r + 0.587 * $g + 0.114 * $b)/255;

    if ($color < 0.5)
        $color = '#000000'; // bright colors - black font
    else
        $color = '#ffffff'; // dark colors - white font

    return $color;
}
GregV
fuente
1

Como extensión Kotlin / Android:

fun Int.getContrastColor(): Int {
    // Counting the perceptive luminance - human eye favors green color...
    val a = 1 - (0.299 * Color.red(this) + 0.587 * Color.green(this) + 0.114 * Color.blue(this)) / 255
    return if (a < 0.5) Color.BLACK else Color.WHITE
}
Gabriel
fuente
1

Una implementación para el objetivo-c

+ (UIColor*) getContrastColor:(UIColor*) color {
    CGFloat red, green, blue, alpha;
    [color getRed:&red green:&green blue:&blue alpha:&alpha];
    double a = ( 0.299 * red + 0.587 * green + 0.114 * blue);
    return (a > 0.5) ? [[UIColor alloc]initWithRed:0 green:0 blue:0 alpha:1] : [[UIColor alloc]initWithRed:255 green:255 blue:255 alpha:1];
}
Andreas Lytter
fuente
2
Debe agregar alguna descripción aquí para que otros usuarios sepan lo que está sucediendo en su código
Michael
1

iOS Swift 3.0 (extensión UIColor):

func isLight() -> Bool
{
    if let components = self.cgColor.components, let firstComponentValue = components[0], let secondComponentValue = components[1], let thirdComponentValue = components[2] {
        let firstComponent = (firstComponentValue * 299)
        let secondComponent = (secondComponentValue * 587)
        let thirdComponent = (thirdComponentValue * 114)
        let brightness = (firstComponent + secondComponent + thirdComponent) / 1000

        if brightness < 0.5
        {
            return false
        }else{
            return true
        }
    }  

    print("Unable to grab components and determine brightness")
    return nil
}
Josh O'Connor
fuente
1
La función está funcionando como se esperaba, pero tenga cuidado con el revestimiento y la fundición
forzada
1
Genial, mira mi ejemplo para Swift 4 a continuación, puedes ver la reducción en el código
RichAppz
1

Swift 4 Ejemplo:

extension UIColor {

    var isLight: Bool {
        let components = cgColor.components

        let firstComponent = ((components?[0]) ?? 0) * 299
        let secondComponent = ((components?[1]) ?? 0) * 587
        let thirdComponent = ((components?[2]) ?? 0) * 114
        let brightness = (firstComponent + secondComponent + thirdComponent) / 1000

        return !(brightness < 0.6)
    }

}

ACTUALIZACIÓN : se encontró que 0.6era un mejor banco de pruebas para la consulta

RichAppz
fuente
En realidad, es muy probable que falle en varias situaciones, ya que supone un espacio de color RGB. El número de elementos CGColor.componentsvaría según el espacio de color: por ejemplo, UIColor.whitecuando se convierte en un CGColor, solo tiene dos: que [1.0, 1.0]representa un color en escala de grises (en blanco) con un alfa completo. Una mejor manera de extraer los elementos RGB de un UIColor esUIColor.getRed(_ red:, green:, blue:, alpha:)
Scott Matthewman el
1

Tenga en cuenta que hay un algoritmo para esto en la biblioteca de cierre de google que hace referencia a una recomendación de w3c: http://www.w3.org/TR/AERT#color-contrast . Sin embargo, en esta API, proporciona una lista de colores sugeridos como punto de partida.

/**
 * Find the "best" (highest-contrast) of the suggested colors for the prime
 * color. Uses W3C formula for judging readability and visual accessibility:
 * http://www.w3.org/TR/AERT#color-contrast
 * @param {goog.color.Rgb} prime Color represented as a rgb array.
 * @param {Array<goog.color.Rgb>} suggestions Array of colors,
 *     each representing a rgb array.
 * @return {!goog.color.Rgb} Highest-contrast color represented by an array.
 */
goog.color.highContrast = function(prime, suggestions) {
  var suggestionsWithDiff = [];
  for (var i = 0; i < suggestions.length; i++) {
    suggestionsWithDiff.push({
      color: suggestions[i],
      diff: goog.color.yiqBrightnessDiff_(suggestions[i], prime) +
          goog.color.colorDiff_(suggestions[i], prime)
    });
  }
  suggestionsWithDiff.sort(function(a, b) { return b.diff - a.diff; });
  return suggestionsWithDiff[0].color;
};


/**
 * Calculate brightness of a color according to YIQ formula (brightness is Y).
 * More info on YIQ here: http://en.wikipedia.org/wiki/YIQ. Helper method for
 * goog.color.highContrast()
 * @param {goog.color.Rgb} rgb Color represented by a rgb array.
 * @return {number} brightness (Y).
 * @private
 */
goog.color.yiqBrightness_ = function(rgb) {
  return Math.round((rgb[0] * 299 + rgb[1] * 587 + rgb[2] * 114) / 1000);
};


/**
 * Calculate difference in brightness of two colors. Helper method for
 * goog.color.highContrast()
 * @param {goog.color.Rgb} rgb1 Color represented by a rgb array.
 * @param {goog.color.Rgb} rgb2 Color represented by a rgb array.
 * @return {number} Brightness difference.
 * @private
 */
goog.color.yiqBrightnessDiff_ = function(rgb1, rgb2) {
  return Math.abs(
      goog.color.yiqBrightness_(rgb1) - goog.color.yiqBrightness_(rgb2));
};


/**
 * Calculate color difference between two colors. Helper method for
 * goog.color.highContrast()
 * @param {goog.color.Rgb} rgb1 Color represented by a rgb array.
 * @param {goog.color.Rgb} rgb2 Color represented by a rgb array.
 * @return {number} Color difference.
 * @private
 */
goog.color.colorDiff_ = function(rgb1, rgb2) {
  return Math.abs(rgb1[0] - rgb2[0]) + Math.abs(rgb1[1] - rgb2[1]) +
      Math.abs(rgb1[2] - rgb2[2]);
};
Pablo
fuente
0

Si está manipulando espacios de color para obtener un efecto visual, generalmente es más fácil trabajar en HSL (Tono, Saturación y Luminosidad) que en RGB. Mover colores en RGB para dar efectos naturalmente agradables tiende a ser bastante difícil desde el punto de vista conceptual, mientras que la conversión a HSL, la manipulación allí, luego la conversión de nuevo es más intuitiva en el concepto e invariablemente da mejores resultados.

Wikipedia tiene una buena introducción a HSL y al HSV estrechamente relacionado. Y hay un código gratuito alrededor de la red para hacer la conversión (por ejemplo, aquí hay una implementación de JavaScript )

La transformación precisa que utilizas es cuestión de gustos, pero personalmente pensé que invertir los componentes de Tono y Luminosidad seguramente generaría un buen color de alto contraste como primera aproximación, pero puedes buscar fácilmente efectos más sutiles.

Cruachan
fuente
1
Sí, pero considere también que el ojo humano puede ver el verde de manera mucho más dominante que otros colores, y el azul menos (por eso el azul obtiene menos bits de color en los formatos de imagen).
wchargin
1
En efecto. Si nos vamos a mudar a HSL, también podríamos dar el salto completo a YUV y tener en cuenta la percepción humana.
David Bradbury
0

Puede tener cualquier texto de tono en cualquier fondo de tono y asegurarse de que sea legible. Lo hago todo el tiempo. Hay una fórmula para esto en Javascript en texto legible en color - STW * Como dice en ese enlace, la fórmula es una variación en el cálculo de ajuste de gamma inverso, aunque en mi humilde opinión es un poco más manejable. Los menús en el lado derecho de ese enlace y sus páginas asociadas usan colores generados al azar para texto y fondo, siempre legibles. Entonces sí, claramente se puede hacer, no hay problema.

Dave Collier
fuente
0

Una variación de Android que también captura el alfa.

(gracias @ thomas-vos)

/**
 * Returns a colour best suited to contrast with the input colour.
 *
 * @param colour
 * @return
 */
@ColorInt
public static int contrastingColour(@ColorInt int colour) {
    // XXX /programming/1855884/determine-font-color-based-on-background-color

    // Counting the perceptive luminance - human eye favors green color...
    double a = 1 - (0.299 * Color.red(colour) + 0.587 * Color.green(colour) + 0.114 * Color.blue(colour)) / 255;
    int alpha = Color.alpha(colour);

    int d = 0; // bright colours - black font;
    if (a >= 0.5) {
        d = 255; // dark colours - white font
    }

    return Color.argb(alpha, d, d, d);
}
Brill Pappin
fuente
0

baseVersión R de la respuesta de @ Gacek para obtener luminance(puede aplicar su propio umbral fácilmente)

# vectorized
luminance = function(col) c(c(.299, .587, .114) %*% col2rgb(col)/255)

Uso:

luminance(c('black', 'white', '#236FAB', 'darkred', '#01F11F'))
# [1] 0.0000000 1.0000000 0.3730039 0.1629843 0.5698039
MichaelChirico
fuente
0

Basado en la respuesta de Gacek , y después de analizar el ejemplo de @ WebSeed con la extensión del navegador WAVE , se me ocurrió la siguiente versión que elige texto en blanco o negro según la relación de contraste (como se define en las Pautas de Accesibilidad al Contenido en la Web (WCAG) 2.1 de W3C ) , en lugar de luminancia.

Este es el código (en javascript):

// As defined in WCAG 2.1
var relativeLuminance = function (R8bit, G8bit, B8bit) {
  var RsRGB = R8bit / 255.0;
  var GsRGB = G8bit / 255.0;
  var BsRGB = B8bit / 255.0;

  var R = (RsRGB <= 0.03928) ? RsRGB / 12.92 : Math.pow((RsRGB + 0.055) / 1.055, 2.4);
  var G = (GsRGB <= 0.03928) ? GsRGB / 12.92 : Math.pow((GsRGB + 0.055) / 1.055, 2.4);
  var B = (BsRGB <= 0.03928) ? BsRGB / 12.92 : Math.pow((BsRGB + 0.055) / 1.055, 2.4);

  return 0.2126 * R + 0.7152 * G + 0.0722 * B;
};

var blackContrast = function(r, g, b) {
  var L = relativeLuminance(r, g, b);
  return (L + 0.05) / 0.05;
};

var whiteContrast = function(r, g, b) {
  var L = relativeLuminance(r, g, b);
  return 1.05 / (L + 0.05);
};

// If both options satisfy AAA criterion (at least 7:1 contrast), use preference
// else, use higher contrast (white breaks tie)
var chooseFGcolor = function(r, g, b, prefer = 'white') {
  var Cb = blackContrast(r, g, b);
  var Cw = whiteContrast(r, g, b);
  if(Cb >= 7.0 && Cw >= 7.0) return prefer;
  else return (Cb > Cw) ? 'black' : 'white';
};

Puede encontrar un ejemplo de trabajo en mi bifurcación del codepen de @ WebSeed, que produce cero errores de bajo contraste en WAVE.

Seirios
fuente