Como verificar se a cor hexadecimal é “muito preta”?

111

Estou tentando avaliar o escurecimento de uma cor escolhida por um seletor de cores para ver se é "muito preto" e, em caso afirmativo, defina-o como branco. Achei que poderia usar os primeiros caracteres do valor hexadecimal para fazer isso. Está funcionando, mas também está trocando algumas cores "claras" legítimas.

Eu tenho um código fazendo isso:

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

Deve haver uma maneira mais eficiente com a matemática hexadecimal de saber que uma cor ultrapassou um certo nível de escuridão? Como se lightcolor + "algum valor hexadecimal" <= "algum valor hexadecimal", defina-o como branco.

Eu adicionei tinyColor, que pode ser útil para isso, mas não tenho certeza.

Dshiz
fonte
1
Você já tentou pegar um seletor de cores e verificar os valores? Percebi que quando R, G e B estão todos abaixo de ~ 70 fica escuro. Esta pode não ser a maneira correta, mas é uma.
Rick Kuipers de
1
Como você já está usando tinyColor, transforme a cor em HSL e dê uma olhada no componente L. 1 = branco, 0 = preto
Andreas de
4
@Andreas HSL leveza não leva em consideração a percepção humana. Um valor L de 0,5 terá um brilho percebido diferente para diferentes matizes.
Alnitak de
1
@Alnitak Você está certo, mas a descrição do TO não é tão precisa. Portanto, qualquer valor abaixo de 3/8 poderia ser escuro o suficiente para seu propósito.
Andreas de
1
@Andreas, isso depende - se você olhar para os valores de luminância da ITU em minha resposta, verá que o azul é percebido como apenas 1/10 do brilho do verde.
Alnitak de

Respostas:

226

Você deve extrair os três componentes RGB individualmente e, em seguida, usar uma fórmula padrão para converter os valores RGB resultantes em seu brilho percebido.

Supondo uma cor 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 maio de 2014 tinycolorpassou a ter uma getBrightness()função, embora usando os fatores de ponderação CCIR601 em vez dos ITU-R acima.

EDITAR

O intervalo de valor luma resultante é 0..255, onde 0 é o mais escuro e 255 é o mais claro. Valores maiores que 128 são considerados leves por tinycolor. (copiado descaradamente dos comentários de @ pau.moreno e @Alnitak)

Alnitak
fonte
11
não vejo alguma manipulação de bits boa em javascript por um tempo. coisas legais. en.wikipedia.org/wiki/Rec._709#Luma_coefficients
jbabey
2
Bom código, mas após o teste, sugiro var luma = (r + g + b) / 3; if (luma <128) {// será mais útil. }
Terry Lin
2
@TerryLin Por quê? Os coeficientes fornecidos são os valores padrão ITU que permitem o fato de que o verde é percebido com mais brilho do que o vermelho (e depois o azul).
Alnitak
1
O lumaintervalo de valores resultante é 0..255, onde 0é o mais escuro e 255é o mais claro (a soma dos três coeficientes é um).
pau.moreno
1
@gabssnake apenas desde maio de 2014, e o isDark()limite é codificado em 128
Alnitak
21

A biblioteca TinyColor (você já mencionou) oferece várias funções para inspecionar e manipular cores, entre elas:

Skalee
fonte
15

Encontrei esta função WooCommerce Wordpress PHP ( wc_hex_is_light ) e converti para JavaScript. Funciona bem!

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
fonte
1
Muito legal, obrigado! Eu testei com várias cores, a detecção estava correta com todas elas :)
David Dal Busco
colorIsDarkOrLight (color) {var hex = color.replace ("#", ""); var c_r, c_g, c_b, brilho = ""; 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); brilho = (c_r * 299 + c_g * 587 + c_b * 114) / 1000; } else {c_r = parseInt (hex.substr (0, 2), 16); c_g = parseInt (hex.substr (2, 2), 16); c_b = parseInt (hex.substr (4, 2), 16); } retornar brilho> 155; },
Pedro Henrique
Use com hex 3 caracteres e 6
Pedro Henrique
6

Você pode calcular a luminância :

A luminância é, portanto, um indicador de quão brilhante a superfície aparecerá.

Portanto, é ótimo escolher se o texto deve ser branco ou preto.

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

O método acima permite que você passe a cor em formatos diferentes, mas o algoritmo é basicamente apenas em luminance_get.

Quando o usei, estava definindo a cor para preto se a luminância fosse maior que 180, branco caso contrário.

Robin
fonte
5

Há uma distinção importante aqui entre luminância e brilho. Luminância, no final do dia, é uma medida de quanta energia viaja por uma determinada área e ignora completamente como nossos sistemas perceptivos percebem essa energia. O brilho, por outro lado, é uma medida de como percebemos essa energia e leva em consideração a relação entre a luminância e nosso sistema perceptivo. (Como ponto de confusão, existe um termo chamado luminância relativa, que parece ser usado como sinônimo de termos de brilho. Isso me enganou bem).

Para ser preciso, você está procurando por "brilho" ou "valor" ou "luminância relativa", como outros sugeriram. Você pode calcular isso de várias maneiras diferentes (como ser humano!) Http://en.wikipedia.org/wiki/HSL_and_HSV#Lightness

  1. Tire o máximo de R, G e B.
  2. Tire a média do máximo e do mínimo de R, G e B.
  3. Faça a média de todos os três.
  4. Use alguma média ponderada como outros sugeriram aqui.
David Nguyen
fonte
AFAIK apenas o cálculo luma descrito na página da Wikipedia é um modelo baseado na percepção.
Alnitak de
2
É bom apontar a diferença entre a energia da luz física e o brilho percebido, mas acho que você confundiu as coisas. A seção do artigo da Wikipedia que você vinculou tem um quarto ponto, que afirma "Uma alternativa mais perceptualmente relevante é usar luma, Y ′, como uma dimensão de leveza" (grifo meu) e, em seguida, dá a fórmula apresentada em Alnitak's e as respostas de Robin. Em outras palavras, o método que você omitiu e não recomendou é o que melhor corresponde à percepção humana.
John Y
@JohnY sim, é o que eu estava tentando dizer - ele deixou de fora o único que realmente corresponde ao resto de sua resposta.
Alnitak de
Sim, descobri que eu era o único confuso aqui. Estou bem com isso :) Eu só queria deixar claro que existe uma diferença entre energia e percepção. Vou atualizar minha resposta de acordo.
David Nguyen de
4

Este trabalho com hex, por exemplo #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;
}

Você pode alterá-lo para retornar trueou falsepor alteração

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

para

return (yiq < 40);
TheCrazyProfessor
fonte
@Vivek isso porque você precisa usar valores hexadecimais completos (# 000000 e #ffffff)
TheCrazyProfessor,
2

Uma possível solução seria converter sua cor de RGB para HSB . HSB significa matiz, saturação e brilho (também conhecido como HSV, onde V é o valor). Então você tem apenas um parâmetro para verificar: brilho.

Ohad
fonte
1

Sei que essa conversa já existe há alguns anos, mas ainda é relevante. Eu queria acrescentar que minha equipe estava tendo o mesmo problema em Java (SWT) e achei um pouco mais 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
fonte