Vérifiez si UIColor est sombre ou clair?


107

Je dois déterminer si un UIColor sélectionné (choisi par l'utilisateur) est sombre ou clair, afin que je puisse changer la couleur d'une ligne de texte qui se trouve au-dessus de cette couleur, pour une meilleure lisibilité.

Voici un exemple en Flash / Actionscript (avec démo): http://web.archive.org/web/20100102024448/http://theflashblog.com/?p=173

Des pensées?

Bravo, André

METTRE À JOUR

Merci aux suggestions de tous, voici le code de travail:

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

Encore merci :)


On dirait que certaines couleurs n'ont pas 3 composants, comme UIColor.black.
Glenn Howes

Réponses:


75

Le W3C propose les éléments suivants: http://www.w3.org/WAI/ER/WD-AERT/#color-contrast

Si vous ne faites que du texte noir ou blanc, utilisez le calcul de luminosité des couleurs ci-dessus. S'il est inférieur à 125, utilisez du texte blanc. S'il est supérieur ou égal à 125, utilisez du texte noir.

edit 1: biais vers le texte noir. :)

edit 2: La formule à utiliser est ((valeur rouge * 299) + (valeur verte * 587) + (valeur bleue * 114)) / 1000.


savez-vous comment obtenir les valeurs rouge, verte et bleue d'une couleur?
Andre

Vous pouvez utiliser NSArray *components = (NSArray *)CGColorGetComponents([UIColor CGColor]);pour obtenir un tableau des composants de couleur, y compris l'alpha. Les documents ne spécifient pas dans quel ordre ils sont, mais je suppose que ce serait rouge, vert, bleu, alpha. De plus, d'après la documentation, "la taille du tableau est supérieure d'un au nombre de composants de l'espace colorimétrique pour la couleur". - ça ne dit pas pourquoi ...
Jasarien

C'est étrange ... en utilisant: NSArray * components = (NSArray *) CGColorGetComponents (newColor.CGColor); NSLog (@ "mes composants de couleur% f% f% f% f", composants [0], composants [1], composants [2], composants [3]); juste pour voir si je peux obtenir les valeurs, il semble que seul l'index 0 change, les autres resteront les mêmes, quelle que soit la couleur que je choisis. Une idée pourquoi?
Andre

Je l'ai. Vous ne pouvez pas utiliser NSArray. Utilisez plutôt ceci: const CGFloat * components = CGColorGetComponents (newColor.CGColor); De plus, l'ordre est: le rouge correspond aux composants [0]; le vert est les composants [1]; le bleu est les composants [2]; alpha est des composants [3];
Andre

Ah, mon mal. Je pensais qu'il renvoyait un CFArrayRef, pas un tableau C. Désolé!
Jasarien

44

Voici une extension Swift (3) pour effectuer cette vérification.

Cette extension fonctionne avec les couleurs en niveaux de gris. Cependant, si vous créez toutes vos couleurs avec l'initialiseur RVB et n'utilisez pas les couleurs intégrées telles que UIColor.blacket UIColor.white, vous pouvez éventuellement supprimer les vérifications supplémentaires.

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

Tests:

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

Remarque: mise à jour vers Swift 3 7/12/18


6
Fonctionne très bien. Avec Swift 2.0 - J'ai eu une erreur de compilation pour le calcul de la luminosité: "L'expression était trop complexe pour être résolue dans un temps raisonnable; envisagez de diviser l'expression en sous-expressions distinctes". Je l'ai corrigé en ajoutant le type: "let bright = (((components [0] * 299.0) as CGFloat) + ((components [1] * 587.0) as CGFloat) + ((components [2] * 114.0)) as CGFloat ) / (1000.0 comme CGFloat) "
GK100

1
J'ai eu le même problème avec Swift 2.1. J'ai résolu ce problème en créant simplement des variables pour les composants au lieu d'y accéder avec components[0]. Comme ça:let componentColorX: CGFloat = components[1]
petritz

1
Xcode me dit que la luminosité = "L'expression était trop complexe pour être résolue dans un délai raisonnable; envisagez de diviser l'expression en sous-expressions distinctes"
daidai

daidai, simplifiez simplement l'expression. La division à la fin est inutile. Nous n'avons pas besoin de travailler dans la plage 0 - 1. Ne divisez pas par 1000, vérifiez simplement si la valeur est supérieure à 500 à la place.
aronspring

32

En utilisant la réponse d'Erik Nedwidek, j'ai trouvé ce petit extrait de code pour une inclusion facile.

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

J'ai utilisé ce code, fonctionné parfaitement, mais je ne sais pas quand j'ai défini [UIColor blackColor], il renvoie obscuritéScore = 149,685. Pouvez-vous expliquer pourquoi cela se produit?
DivineDesert

3
Ce code ne fonctionnera pas avec les couleurs non RVB telles que UIColor whiteColor, grayColor, blackColor.
rmaddy

@rmaddy pourquoi est-ce? ou pourquoi les couleurs RVB whiteColor, grayColor et blackColor ne sont-elles pas?
mattsven

1
@rmaddy Comment puis-je déterminer par programmation la différence entre les couleurs de l'espace colorimétrique RVB et les niveaux de gris?
mattsven

1
@maddy Nevermind! Merci pour l'aide - 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
}

Pour moi, celui-ci est le meilleur. Vous pouvez même définir la plage de blanc dans le chèque en fonction de vos besoins et est super simple et native. Merci.
Andreas777

9

Version 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
Cela ne fonctionnera probablement pas pour les couleurs système par défaut comme UIColor.white car il n'aura que 2 composants, ce n'est pas une représentation RVB.
Skrew

7

Ma solution à ce problème dans une catégorie (tirée d'autres réponses ici). Fonctionne également avec les couleurs en niveaux de gris, ce qu'aucune des autres réponses ne fait au moment de la rédaction.

@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

belle manipulation des couleurs non RVB - fonctionne comme un charme.
hatunike

5

Extension Swift 3 plus 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
Cela ne fonctionnera probablement pas pour les couleurs système par défaut comme UIColor.white car il n'aura que 2 composants, ce n'est pas une représentation RVB.
Skrew

Vrai! Vous pouvez convertir la couleur en chaîne hexadécimale, puis revenir en UIColor pour en tenir compte.
Sunkas

3

Si vous préférez la version bloc:

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

Pour tout ce qui n'est pas grisâtre, l'inverse RVB d'une couleur est généralement très contrasté avec elle. La démo inverse simplement la couleur et la désature (la convertit en gris).

Mais générer une belle combinaison de couleurs apaisante est assez compliqué. Regarder :

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


1
Si seulement il y avait une version iPhone de cela: D
Andre

Donc, si j'ai les valeurs RVB d'une couleur (ce que je ne sais pas faire) et que j'ai créé une nouvelle couleur avec l'inverse de ces valeurs, cela devrait fonctionner à un niveau très basique?
Andre

2

La méthode suivante consiste à trouver la couleur claire ou foncée en langage Swift basé sur la couleur blanche.

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
}

La méthode suivante consiste à trouver la couleur claire ou foncée dans Swift à l'aide de composants de couleur.

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

Pour moi, n'utiliser que CGColorGetComponents n'a pas fonctionné, j'obtiens 2 composants pour UIColors comme le blanc. Je dois donc d'abord vérifier l'espace colorimétrique du modèle. C'est ce que j'ai trouvé qui a fini par être la version rapide de la réponse de @ mattsven.

Espace colorimétrique pris à partir d'ici: 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 a la méthode suivante pour convertir en espace colorimétrique 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 5Version ajoutée :

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

1
Cela ne fonctionne que s'il UIColors'agit d'une couleur en niveaux de gris. Une couleur RVB aléatoire ne fonctionnera pas avec ce code.
rmaddy

0

Si vous voulez trouver la luminosité de la couleur, voici un pseudo code:

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

S'il est> 0,5, il est clair, sinon sombre.


2
Vous ne pouvez pas pondérer chacune des couleurs de manière égale. Nos yeux ont un biais contre le bleu et dans une moindre mesure le rouge.
Erik Nedwidek

@ErikNedwidek: Très bien, la question se posait simplement pour la luminosité d'une couleur :)
Bryan Denny
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.