Verifique se UIColor está escuro ou claro?

107

Preciso determinar se um UIColor selecionado (escolhido pelo usuário) é escuro ou claro, para que eu possa alterar a cor de uma linha de texto que fica em cima dessa cor, para melhor legibilidade.

Aqui está um exemplo em Flash / Actionscript (com demonstração): http://web.archive.org/web/20100102024448/http://theflashblog.com/?p=173

Alguma ideia?

Saúde Andre

ATUALIZAR

Graças às sugestões de todos, aqui está o código de trabalho:

- (void) updateColor:(UIColor *) newColor
{
    const CGFloat *componentColors = CGColorGetComponents(newColor.CGColor);

    CGFloat colorBrightness = ((componentColors[0] * 299) + (componentColors[1] * 587) + (componentColors[2] * 114)) / 1000;
    if (colorBrightness < 0.5)
    {
        NSLog(@"my color is dark");
    }
    else
    {
        NSLog(@"my color is light");
    }
}

Obrigado mais uma vez :)

Andre
fonte
Parece que algumas cores não têm 3 componentes, como UIColor.black.
Glenn Howes

Respostas:

75

W3C tem o seguinte: http://www.w3.org/WAI/ER/WD-AERT/#color-contrast

Se você estiver fazendo apenas texto em preto ou branco, use o cálculo de brilho de cor acima. Se estiver abaixo de 125, use texto branco. Se for 125 ou superior, use texto preto.

edição 1: tendência para texto preto. :)

editar 2: A fórmula a ser usada é ((valor vermelho * 299) + (valor verde * 587) + (valor azul * 114)) / 1000.

Erik Nedwidek
fonte
você sabe como obter os valores de vermelho, verde e azul de uma cor?
Andre
Você pode usar NSArray *components = (NSArray *)CGColorGetComponents([UIColor CGColor]);para obter uma matriz de componentes de cores, incluindo o alfa. Os documentos não especificam a ordem em que estão, mas presumo que seja vermelho, verde, azul, alfa. Além disso, nos documentos, "o tamanho da matriz é um a mais do que o número de componentes do espaço de cores da cor." - não diz por que ...
Jasarien
Isso é estranho ... usando: NSArray * components = (NSArray *) CGColorGetComponents (newColor.CGColor); NSLog (@ "meus componentes de cor% f% f% f% f", componentes [0], componentes [1], componentes [2], componentes [3]); só para ver se consigo obter os valores, parece que apenas o índice 0 muda, os outros permanecem os mesmos, independentemente da cor que escolher. Alguma ideia do porquê?
Andre
Entendi. Você não pode usar NSArray. Em vez disso, use: const CGFloat * components = CGColorGetComponents (newColor.CGColor); Além disso, a ordem é: vermelho é componentes [0]; o verde são os componentes [1]; o azul são os componentes [2]; alfa é componentes [3];
Andre
Ah, que pena. Eu pensei que ele retornou um CFArrayRef, não um array C. Desculpe!
Jasarien
44

Aqui está uma extensão Swift (3) para realizar esta verificação.

Esta extensão funciona com cores em tons de cinza. No entanto, se você estiver criando todas as suas cores com o inicializador RGB e não usando as cores embutidas como UIColor.blacke UIColor.white, então possivelmente você pode remover as marcas adicionais.

extension UIColor {

    // Check if the color is light or dark, as defined by the injected lightness threshold.
    // Some people report that 0.7 is best. I suggest to find out for yourself.
    // A nil value is returned if the lightness couldn't be determined.
    func isLight(threshold: Float = 0.5) -> Bool? {
        let originalCGColor = self.cgColor

        // Now we need to convert it to the RGB colorspace. UIColor.white / UIColor.black are greyscale and not RGB.
        // If you don't do this then you will crash when accessing components index 2 below when evaluating greyscale colors.
        let RGBCGColor = originalCGColor.converted(to: CGColorSpaceCreateDeviceRGB(), intent: .defaultIntent, options: nil)
        guard let components = RGBCGColor?.components else {
            return nil
        }
        guard components.count >= 3 else {
            return nil
        }

        let brightness = Float(((components[0] * 299) + (components[1] * 587) + (components[2] * 114)) / 1000)
        return (brightness > threshold)
    }
}

Testes:

func testItWorks() {
    XCTAssertTrue(UIColor.yellow.isLight()!, "Yellow is LIGHT")
    XCTAssertFalse(UIColor.black.isLight()!, "Black is DARK")
    XCTAssertTrue(UIColor.white.isLight()!, "White is LIGHT")
    XCTAssertFalse(UIColor.red.isLight()!, "Red is DARK")
}

Nota: Atualizado para Swift 3 07/12/18

Josh-fuggle
fonte
6
Funciona bem. Com Swift 2.0 - obtive um erro do compilador para o cálculo de brilho: "A expressão era muito complexa para ser resolvida em um tempo razoável; considere dividir a expressão em subexpressões distintas". Eu o fixei adicionando o tipo: "deixe brilho = (((componentes [0] * 299,0) como CGFloat) + ((componentes [1] * 587,0) como CGFloat) + ((componentes [2] * 114,0)) como CGFloat ) / (1000,0 como CGFloat) "
GK100
1
Eu tive o mesmo problema com o Swift 2.1. Resolvi esse problema simplesmente criando variáveis ​​para os componentes em vez de acessá-los com components[0]. Assim:let componentColorX: CGFloat = components[1]
petritz
1
O Xcode me diz que o brilho = "A expressão era muito complexa para ser resolvida em um tempo razoável; considere dividir a expressão em subexpressões distintas"
daidai
daidai, apenas simplifique a expressão. A divisão no final é desnecessária. Não precisamos trabalhar no intervalo de 0 a 1. Não divida por 1000, apenas verifique se o valor é maior que 500.
aronspring
32

Usando a resposta de Erik Nedwidek, eu vim com aquele pequeno trecho de código para fácil inclusão.

- (UIColor *)readableForegroundColorForBackgroundColor:(UIColor*)backgroundColor {
    size_t count = CGColorGetNumberOfComponents(backgroundColor.CGColor);
    const CGFloat *componentColors = CGColorGetComponents(backgroundColor.CGColor);

    CGFloat darknessScore = 0;
    if (count == 2) {
        darknessScore = (((componentColors[0]*255) * 299) + ((componentColors[0]*255) * 587) + ((componentColors[0]*255) * 114)) / 1000;
    } else if (count == 4) {
        darknessScore = (((componentColors[0]*255) * 299) + ((componentColors[1]*255) * 587) + ((componentColors[2]*255) * 114)) / 1000;
    }

    if (darknessScore >= 125) {
        return [UIColor blackColor];
    }

    return [UIColor whiteColor];
}
Remy Vanherweghem
fonte
Usei esse código, funcionou perfeitamente, mas não sei quando configurei [UIColor blackColor], ele retorna darknessScore = 149.685. Você pode explicar por que isso está acontecendo?
DivineDesert de
3
Este código não funciona com cores não RGB, como UIColor whiteColor, grayColor, blackColor.
rmaddy
@rmaddy por que isso? ou por que não são cores RGB whiteColor, grayColor e blackColor?
mattsven de
1
@rmaddy Como posso diferenciar programaticamente entre cores no espaço de cores RGB e tons de cinza?
mattsven de
1
@maddy Nevermind! Obrigado pela ajuda - stackoverflow.com/questions/1099569/…
mattsven
31

Swift3

extension UIColor {
    var isLight: Bool {
        var white: CGFloat = 0
        getWhite(&white, alpha: nil)
        return white > 0.5
    }
}

// Usage
if color.isLight {
    label.textColor = UIColor.black
} else {
    label.textColor = UIColor.white
}
neoneye
fonte
Para mim, este é o melhor. Você pode até definir a faixa de branco no cheque para atender às suas necessidades e é super simples e nativo. Obrigado.
Andreas777
9

Versão Swift 4

extension UIColor {
    func isLight() -> Bool {
        guard let components = cgColor.components, components.count > 2 else {return false}
        let brightness = ((components[0] * 299) + (components[1] * 587) + (components[2] * 114)) / 1000
        return (brightness > 0.5)
    }
}
Kaiyuan Xu
fonte
1
Isso provavelmente não funcionará com cores padrão do sistema como UIColor.white, pois terá apenas 2 componentes, não é uma representação RGB.
Skrew
7

Minha solução para este problema em uma categoria (extraída de outras respostas aqui). Também funciona com cores em tons de cinza, o que nenhuma das outras respostas fazia no momento.

@interface UIColor (Ext)

    - (BOOL) colorIsLight;

@end

@implementation UIColor (Ext)

    - (BOOL) colorIsLight {
        CGFloat colorBrightness = 0;

        CGColorSpaceRef colorSpace = CGColorGetColorSpace(self.CGColor);
        CGColorSpaceModel colorSpaceModel = CGColorSpaceGetModel(colorSpace);

        if(colorSpaceModel == kCGColorSpaceModelRGB){
            const CGFloat *componentColors = CGColorGetComponents(self.CGColor);

            colorBrightness = ((componentColors[0] * 299) + (componentColors[1] * 587) + (componentColors[2] * 114)) / 1000;
        } else {
            [self getWhite:&colorBrightness alpha:0];
        }

        return (colorBrightness >= .5f);
    }

@end
Mattsven
fonte
bom manuseio de cores não RGB - funciona perfeitamente.
hatunike 01 de
5

Extensão Swift 3 mais simples:

extension UIColor {
    func isLight() -> Bool {
        guard let components = cgColor.components else { return false }
        let redBrightness = components[0] * 299
        let greenBrightness = components[1] * 587
        let blueBrightness = components[2] * 114
        let brightness = (redBrightness + greenBrightness + blueBrightness) / 1000
        return brightness > 0.5
    }
}
Sunkas
fonte
2
Isso provavelmente não funcionará com cores padrão do sistema como UIColor.white, pois terá apenas 2 componentes, não é uma representação RGB.
Skrew
Verdade! Você poderia converter a cor em hexadecimal e, em seguida, voltar para UIColor para contabilizar isso.
Sunkas
3

Se você preferir a versão em bloco:

BOOL (^isDark)(UIColor *) = ^(UIColor *color){
    const CGFloat *component = CGColorGetComponents(color.CGColor);
    CGFloat brightness = ((component[0] * 299) + (component[1] * 587) + (component[2] * 114)) / 1000;

    if (brightness < 0.75)
        return  YES;
    return NO;
};
kakilangit
fonte
2

Para tudo que não é acinzentado, o inverso RGB de uma cor geralmente é altamente contrastado com ela. A demonstração apenas inverte a cor e remove a saturação (converte para um cinza).

Mas gerar uma combinação de cores suave e agradável é bastante complicado. Olhe para :

http://particletree.com/notebook/calculating-color-contrast-for-legible-text/

rep_movsd
fonte
1
Se ao menos houvesse uma versão para iPhone disso: D
Andre
Então, se eu obtiver os valores RGB de uma cor (o que não sei como fazer) e criar uma nova cor com o inverso desses valores, isso deve funcionar em um nível muito básico?
Andre
2

O método a seguir é descobrir que a cor é clara ou escura na linguagem Swift com base na cor branca.

func isLightColor(color: UIColor) -> Bool 
{
   var white: CGFloat = 0.0
   color.getWhite(&white, alpha: nil)

   var isLight = false

   if white >= 0.5
   {
       isLight = true
       NSLog("color is light: %f", white)
   }
   else
   {
      NSLog("Color is dark: %f", white)
   }

   return isLight
}

O método a seguir é descobrir que a cor é clara ou escura no Swift usando componentes de cor.

func isLightColor(color: UIColor) -> Bool 
{
     var isLight = false

     var componentColors = CGColorGetComponents(color.CGColor)

     var colorBrightness: CGFloat = ((componentColors[0] * 299) + (componentColors[1] * 587) + (componentColors[2] * 114)) / 1000;
     if (colorBrightness >= 0.5)
     {
        isLight = true
        NSLog("my color is light")
     }
     else
     {
        NSLog("my color is dark")
     }  
     return isLight
}
abhi
fonte
2

Para mim, usar apenas CGColorGetComponents não funcionou, recebo 2 componentes para UIColors como branco. Portanto, tenho que verificar o espaço de coresModel primeiro. Isso é o que eu descobri que acabou sendo a versão rápida da resposta de @mattsven.

Espaço de cores retirado daqui: https://stackoverflow.com/a/16981916/4905076

extension UIColor {
    func isLight() -> Bool {
        if let colorSpace = self.cgColor.colorSpace {
            if colorSpace.model == .rgb {
                guard let components = cgColor.components, components.count > 2 else {return false}

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

                return (brightness > 0.5)
            }
            else {
                var white : CGFloat = 0.0

                self.getWhite(&white, alpha: nil)

                return white >= 0.5
            }
        }

        return false
    }
Lucho
fonte
2

UIColor tem o seguinte método para converter para o espaço de cores HSB :

- (BOOL)getHue:(CGFloat *)hue saturation:(CGFloat *)saturation brightness:(CGFloat *)brightness alpha:(CGFloat *)alpha;
Cuddy
fonte
1
- (BOOL)isColorLight:(UIColor*)color
{
    CGFloat white = 0;
    [color getWhite:&white alpha:nil];
    return (white >= .85);
}

Swift 5Versão adicionada :

var white: CGFloat = 0.0
color.getWhite(&white, alpha: nil)
return white >= .85 // Don't use white background
Mike Glukhov
fonte
1
Isso só funciona se UIColorfor uma cor em tons de cinza. Uma cor RGB aleatória não funcionará com este código.
rmaddy
0

Se você deseja encontrar o brilho da cor, aqui estão alguns pseudocódigos:

public float GetBrightness(int red, int blue, int green)
{
    float num = red / 255f;
    float num2 = blue / 255f;
    float num3 = green / 255f;
    float num4 = num;
    float num5 = num;
    if (num2 > num4)
        num4 = num2;
    if (num3 > num4)
        num4 = num3;
    if (num2 < num5)
        num5 = num2;
    if (num3 < num5)
        num5 = num3;
    return ((num4 + num5) / 2f);
}

Se for> 0,5, é claro e escuro.

Bryan Denny
fonte
2
Você não pode pesar cada uma das cores igualmente. Nossos olhos têm um viés contra o azul e, em menor medida, o vermelho.
Erik Nedwidek
@ErikNedwidek: Bastante justo, a pergunta feita simplesmente sobre o brilho de uma cor :)
Bryan Denny