Como decidir a cor da fonte em branco ou preto, dependendo da cor de fundo?


218

Eu quero mostrar algumas imagens como este exemplotexto alternativo

A cor do preenchimento é decidida por um campo na base de dados com a cor hexadecimal (ex: ClassX -> Cor: # 66FFFF). Agora, quero mostrar os dados acima de um preenchimento com a cor selecionada (como na imagem acima), mas preciso saber se a cor é escura ou clara, para que eu saiba se as palavras devem estar em branco ou preto. Há algum jeito? tks


Respostas:


345

Com base na minha resposta a uma pergunta semelhante .

Você precisa dividir o código hexadecimal em três partes para obter as intensidades individuais de vermelho, verde e azul. Cada 2 dígitos do código representa um valor em notação hexadecimal (base 16). Não vou entrar em detalhes da conversão aqui, eles são fáceis de procurar.

Depois de ter as intensidades para as cores individuais, você pode determinar a intensidade geral da cor e escolher o texto correspondente.

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

O limite de 186 é baseado na teoria, mas pode ser ajustado ao gosto. Com base nos comentários abaixo, um limite de 150 pode funcionar melhor para você.


Edit: O acima é simples e funciona razoavelmente bem, e parece ter uma boa aceitação aqui no StackOverflow. No entanto, um dos comentários abaixo mostra que isso pode levar ao não cumprimento das diretrizes do W3C em algumas circunstâncias. Com isso, derivo uma forma modificada que sempre escolhe o maior contraste com base nas diretrizes. Se você não precisa estar em conformidade com as regras do W3C, eu continuaria com a fórmula mais simples acima.

A fórmula dada para contraste nas Recomendações do W3C é (L1 + 0.05) / (L2 + 0.05): onde L1está a luminância da cor mais clara e L2é a luminância da mais escura em uma escala de 0,0-1,0. A luminância do preto é 0,0 e o branco é 1,0, portanto, a substituição desses valores permite determinar o que tem maior contraste. Se o contraste para preto for maior que o contraste para branco, use preto; caso contrário, use branco. Dada a luminosidade da cor que você está testando à medida que Lo teste se torna:

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

Isso simplifica algebricamente:

if L > sqrt(1.05 * 0.05) - 0.05

Ou aproximadamente:

if L > 0.179 use #000000 else use #ffffff

A única coisa que resta é calcular L. Essa fórmula também é fornecida nas diretrizes e parece com a conversão de sRGB em RGB linear seguida pela recomendação ITU-R BT.709 para luminância.

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

O limite de 0,179 não deve ser alterado, pois está vinculado às diretrizes do W3C. Se você achar que os resultados não são do seu agrado, tente a fórmula mais simples acima.


2
Tks Mark. Tentei algumas mudanças: calculado em vermelho, verde e azul somente pelo primeiro dígito (menos preciso, mas é o dígito com mais peso) e, em vez dos 186, o 9. usado 9. funciona um pouco melhor para mim, especialmente com os verdes.
DJPB 15/10/10

2
Esta fórmula está errada, muito. Para dar um exemplo, ele fornece ao amarelo #D6B508um valor de 171, portanto, um contraste de branco. O contraste deve ser preto, no entanto (confirmado aqui: webaim.org/resources/contrastchecker )
McGarnagle

1
@ MarkRansom sim, aos meus olhos o preto fica muito melhor com aquele amarelo. Como estou trabalhando com um conjunto limitado de cores, pude mudar o corte de 186 para um valor mais baixo.
21714 McGarnagle

3
Aqui está outra demonstração comparando as duas fórmulas fornecidas nesta resposta. Usando o seletor de cores (no Firefox ou Chrome recente), você pode verificar o contraste em qualquer cor.
Chetstone #

3
Depois de brincar um pouco com a demo do @ chetstone, acho que a fórmula simples funciona melhor para mim, exceto que eu discordo da recomendação de limite. Com base nos resultados do verde puro, tentei definir o limite como 149 e, na minha opinião, funciona muito melhor. Eu fiz um violino realmente idiota para demonstrar isso ; você pode tentar alterar o limite na parte superior traseira para ver a sugestão original.
Miral

30

Não aceito crédito por esse código, pois não é meu, mas deixo aqui para que outras pessoas o encontrem rapidamente no futuro:

Com base na resposta de Mark Ransoms, aqui está um trecho de código para a versão simples:

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;
}

e aqui está o trecho de código da versão avançada:

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 usá-los, basta ligar para:

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

Além disso, obrigado Alxe chetstone.


1
Eu usei a função simples e despojado-lo um pouco mais: caiu os 2 últimos parâmetros e renomeado é simplesmente isDark(bgColor)meu uso é então apenas'color': isDark(color)?'white':'black'
diynevala

1
Caiu como uma luva para mim. Muito obrigado! Fiz isso no php, mas simplesmente convertendo a sintaxe e as funções.
CezarBastos 23/03

18

Que tal isso (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
Isso funciona na maioria das vezes, mas há casos como i.imgur.com/3pOUDe5.jpg que parecem estranhos, a cor do plano de fundo é na verdade rgb (6, 247, 241);
madprops

Aprecio esta é uma maneira relativamente barata para executar um cálculo contraste nos casos em que você não tenha já convertidos a cor para valores RGB (não que isso é muito difícil, passos de matemática apenas extras)
frumbert

12

Além das soluções aritméticas, também é possível usar uma rede neural de IA. A vantagem é que você pode adaptá-lo ao seu próprio gosto e necessidades (ou seja, o texto esbranquiçado em vermelhos saturados brilhantes parece bom e é tão legível quanto o preto).

Aqui está uma demonstração clara de Javascript que ilustra o conceito. Você também pode gerar sua própria fórmula JS diretamente na demonstração.

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

Abaixo estão alguns gráficos que me ajudaram a resolver o problema . No primeiro gráfico, a luminosidade é constante 128, enquanto o matiz e a saturação variam. No segundo gráfico, a saturação é uma constante 255, enquanto a tonalidade e a luminosidade variam.

No primeiro gráfico, a luminosidade é constante 128, enquanto o matiz e a saturação variam:

A saturação é constante 255, enquanto a tonalidade e a luminosidade variam:


8

Aqui está minha solução em 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
É realmente 'perfeito'? Tente fundo verde puro, # 00FF00.
Andreas Rejbrand

É verdade que isso não foi testado para todas as cores ... mas quem usaria um fundo verde puro para qualquer coisa que não fosse projetada para incomodar os usuários?
Mwieczorek

As pessoas @mwieczorek que dependem de conteúdo gerado pelo usuário ou cores selecionadas aleatoriamente o fazem.
Marc Plano-Lesay

4

Com base na resposta de @MarkRansom, criei um script PHP que você pode encontrar aqui:

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 é uma versão rápida da resposta de Mark Ransom como uma extensão do 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 é apenas um exemplo que mudará a cor de uma marca de seleção SVG ao clicar em um elemento. Ele definirá a cor da marca de seleção como preto ou branco com base na cor de fundo do elemento clicado.

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

Eu uso essa função JavaScript para converter rgb/ rgbapara 'white'ou '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';
    }
}

Você pode inserir qualquer um desses formatos e ele produzirá 'black'ou'white'

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

Agora tente isso com um fundo verde puro: # 00FF00.
Andreas Rejbrand

Obrigado por isso! Traduzido para o Swift e usado no meu aplicativo iOS!
Lucas P.

2

A resposta detalhada de Marcos funciona muito bem. Aqui está uma implementação em 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';
}

Em seguida, pode chamar esta função lum([111, 22, 255])para obter branco ou preto.


1

Eu nunca fiz nada assim, mas que tal escrever uma função para verificar os valores de cada uma das cores em relação à cor mediana do Hex 7F (FF / 2). Se duas das três cores forem maiores que 7F, você estará trabalhando com uma cor mais escura.


1

Com base em diferentes entradas do link Tornar as cores de primeiro plano em preto ou branco, dependendo do plano de fundo e desta discussão, criei uma classe de extensão para Color que fornece as cores de contraste necessárias.

O código é o seguinte:

 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

Se, como eu, você estava procurando uma versão RGBA que leve em consideração o alfa, aqui está uma que funciona muito bem para ter 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 é uma versão R da resposta de Mark Ransom, usando apenas a 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, eu estava olhando para a sua resposta, o que parece bom, mas há um pequeno erro nela. Sua função hexToG e hextoB precisam de uma edição menor. O último número em substr é o comprimento da string e, nesse caso, deve ser "2", em vez de 4 ou 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 tem uma contrast()função interessante que funcionou muito bem para mim, consulte http://lesscss.org/functions/#color-operations-contrast

"Escolha qual das duas cores oferece o maior contraste com a outra. Isso é útil para garantir que uma cor seja legível contra um plano de fundo, o que também é útil para conformidade com acessibilidade. Essa função funciona da mesma maneira que a função de contraste no Compass for SASS. De acordo com as WCAG 2.0, as cores são comparadas usando o valor de luma corrigido por gama, e não a luminosidade ".

Exemplo:

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%);
}

Resultado:

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

0

De hex a preto ou branco:

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 da versão do Objective-c para iOS com base na resposta 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

Que tal testar com todas as cores de 24 bits?

Esteja ciente de que o método YIQ retornará uma taxa de contraste mínima de 1,9: 1, que não passa nos testes AA e AAA WCAG2.0, assumindo um limite de 128.

Para o método W3C, ele retornará uma taxa de contraste mínima de 4,58: 1, que passa nos testes AA e AAA para texto grande e no teste AA para texto pequeno, não passa no teste AAA para texto pequeno para todas as cores.


0

Aqui está o meu próprio método que eu tenho usado e até agora não tive problemas 😄

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

Aqui está o meu código para Java Swing com base na incrível resposta 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;
}
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.