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

Andre
fuente
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.

Erik Nedwidek
fuente
¿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

josh-fuggle
fuente
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];
}
Remy Vanherweghem
fuente
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
}
neoneye
fuente
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)
    }
}
Kaiyuan Xu
fuente
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
mattsven
fuente
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
    }
}
Sunkas
fuente
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;
};
kakilangit
fuente
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/

rep_movsd
fuente
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
}
abhi
fuente
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
    }
Lucho
fuente
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;
Cuddy
fuente
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
Mike Glukhov
fuente
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.

Bryan Denny
fuente
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