Compruebe si UIColor es oscuro o brillante.


107

Necesito determinar si un UIColor seleccionado (elegido por el usuario) es oscuro o brillante, para poder cambiar el color de una línea de texto que se encuentra encima de ese color, para una mejor legibilidad.

Aquí hay un ejemplo en Flash / Actionscript (con demostración): http://web.archive.org/web/20100102024448/http://theflashblog.com/?p=173

¿Alguna idea?

Saludos, Andre

ACTUALIZAR

Gracias a las sugerencias de todos, aquí está el código de trabajo:

- (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");
    }
}

Gracias una vez más :)


Parece que algunos colores no tienen 3 componentes, como UIColor.black.
Glenn Howes

Respuestas:


75

W3C tiene lo siguiente: http://www.w3.org/WAI/ER/WD-AERT/#color-contrast

Si solo está haciendo texto en blanco o negro, use el cálculo de brillo de color anterior. Si está por debajo de 125, use texto blanco. Si es 125 o superior, use texto negro.

edición 1: sesgo hacia el texto negro. :)

edición 2: la fórmula a utilizar es ((valor rojo * 299) + (valor verde * 587) + (valor azul * 114)) / 1000.


¿Sabes cómo obtener los valores rojo, verde y azul de un color?
Andre

Puede utilizar NSArray *components = (NSArray *)CGColorGetComponents([UIColor CGColor]);para obtener una matriz de los componentes de color, incluido el alfa. Los documentos no especifican en qué orden están, pero supongo que sería rojo, verde, azul, alfa. Además, según los documentos, "el tamaño de la matriz es uno más que el número de componentes del espacio de color para el color". - no dice por qué ...
Jasarien

Eso es extraño ... usando: NSArray * components = (NSArray *) CGColorGetComponents (newColor.CGColor); NSLog (@ "mis componentes de color% f% f% f% f", componentes [0], componentes [1], componentes [2], componentes [3]); solo para ver si puedo obtener los valores, parece que solo cambia el índice 0, los demás seguirán siendo los mismos, independientemente del color que elija. ¿Alguna idea de por qué?
Andre

Entendido. No puede usar NSArray. Use esto en su lugar: const CGFloat * components = CGColorGetComponents (newColor.CGColor); Además, el orden es: rojo son componentes [0]; el verde es los componentes [1]; el azul es componentes [2]; alfa son componentes [3];
Andre

Ah, mi mal. Pensé que devolvía un CFArrayRef, no una matriz C. ¡Lo siento!
Jasarien

44

Aquí hay una extensión Swift (3) para realizar esta verificación.

Esta extensión funciona con colores en escala de grises. Sin embargo, si está creando todos sus colores con el inicializador RGB y no está usando los colores incorporados como UIColor.blacky UIColor.white, entonces posiblemente pueda eliminar las marcas adicionales.

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

Pruebas:

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: actualizado a Swift 3 7/12/18


6
Funciona genial. Con Swift 2.0, obtuve un error del compilador para el cálculo de brillo: "La expresión era demasiado compleja para resolverse en un tiempo razonable; considere dividir la expresión en distintas sub-expresiones". Lo arreglé agregando el tipo: "dejar brillo = (((componentes [0] * 299.0) como CGFloat) + ((componentes [1] * 587.0) como CGFloat) + ((componentes [2] * 114.0)) como CGFloat ) / (1000.0 as CGFloat) "
GK100

1
Tuve el mismo problema con Swift 2.1. Resolví este problema simplemente creando variables para los componentes en lugar de acceder a ellos con components[0]. Así:let componentColorX: CGFloat = components[1]
petritz

1
Xcode me dice el brillo = "La expresión era demasiado compleja para resolverse en un tiempo razonable; considere dividir la expresión en distintas sub-expresiones"
daidai

daidai, simplemente simplifica la expresión. La división al final es innecesaria. No es necesario que trabajemos en el rango 0 - 1. No divida por 1000, luego simplemente verifique si el valor es mayor que 500.
aronspring

32

Usando la respuesta de Erik Nedwidek, se me ocurrió ese pequeño fragmento de código para facilitar su inclusión.

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

Usé este código, funcionó perfectamente, pero no sé cuándo configuré [UIColor blackColor], devuelve darkScore = 149.685. ¿Puede explicar por qué sucede esto?
DivineDesert

3
Este código no funcionará con colores que no sean RGB como UIColor whiteColor, grayColor, blackColor.
rmaddy

@rmaddy, ¿por qué es eso? o ¿por qué no son los colores RGB whiteColor, grayColor y blackColor?
mattsven

1
@rmaddy ¿Cómo puedo saber mediante programación la diferencia entre colores en el espacio de color RGB y en escala de grises?
mattsven

1
@maddy ¡No importa! Gracias por la ayuda - 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
}

Para mí este es el mejor. Incluso puede establecer un rango de blanco en el cuadro para que se adapte a sus necesidades y es súper simple y nativo. Gracias.
Andreas777

9

Versión 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)
    }
}

1
Eso probablemente no funcionará para los colores del sistema predeterminados como UIColor.white, ya que solo tendrá 2 componentes, no es una representación RGB.
Skrew

7

Mi solución a este problema en una categoría (extraída de otras respuestas aquí). También funciona con colores en escala de grises, lo que al momento de escribir ninguna de las otras respuestas lo hace.

@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

Buen manejo de colores no RGB: funciona a la perfección.
hatunike

5

Extensión Swift 3 más simple:

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

2
Eso probablemente no funcionará para los colores del sistema predeterminados como UIColor.white, ya que solo tendrá 2 componentes, no es una representación RGB.
Skrew

¡Cierto! Puede convertir el color a cadena hexadecimal y luego volver a UIColor para tener en cuenta eso.
Sunkas

3

Si prefiere la versión en bloque:

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

2

Para todo lo que no es grisáceo, el inverso RGB de un color suele contrastarse mucho con él. La demostración simplemente invierte el color y lo desatura (lo convierte en gris).

Pero generar una combinación de colores agradable y relajante es bastante complicado. Mirar :

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


1
Si tan solo hubiera una versión para iPhone de eso: D
Andre

Entonces, si obtuve los valores RGB de un color (que no sé cómo hacer) y creé un nuevo color con el inverso de esos valores, ¿eso debería funcionar en un nivel muy básico?
Andre

2

El siguiente método es encontrar el color claro u oscuro en el lenguaje Swift basado en el color blanco.

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
}

El siguiente método es encontrar el color claro u oscuro en Swift utilizando componentes de color.

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
}

2

Para mí, usar solo CGColorGetComponents no funcionó, obtengo 2 componentes para UIColors como el blanco. Así que primero tengo que comprobar el modelo de espacio de color. Esto es lo que se me ocurrió que terminó siendo la versión rápida de la respuesta de @ mattsven.

Espacio de color tomado de aquí: 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
    }

2

UIColor tiene el siguiente método para convertir al espacio de color HSB :

- (BOOL)getHue:(CGFloat *)hue saturation:(CGFloat *)saturation brightness:(CGFloat *)brightness alpha:(CGFloat *)alpha;

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

Swift 5Versión agregada :

var white: CGFloat = 0.0
color.getWhite(&white, alpha: nil)
return white >= .85 // Don't use white background

1
Esto solo funciona si UIColores un color en escala de grises. Un color RGB aleatorio no funcionará con este código.
rmaddy

0

Si desea encontrar el brillo del color, aquí hay un pseudocódigo:

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

Si es> 0,5, es brillante y, por lo demás, oscuro.


2
No puedes ponderar cada uno de los colores por igual. Nuestros ojos tienen un sesgo contra el azul y en menor medida el rojo.
Erik Nedwidek

@ErikNedwidek: Muy bien, la pregunta simplemente pedía el brillo de un color :)
Bryan Denny
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.