Sprawdź, czy UIColor jest ciemny czy jasny?

107

Muszę określić, czy wybrany kolor UIColor (wybrany przez użytkownika) jest ciemny, czy jasny, aby móc zmienić kolor wiersza tekstu, który znajduje się nad tym kolorem, dla lepszej czytelności.

Oto przykład we Flash / Actionscript (z demo): http://web.archive.org/web/20100102024448/http://theflashblog.com/?p=173

jakieś pomysły?

Pozdrawiam, Andre

AKTUALIZACJA

Dzięki sugestiom wszystkich, oto działający kod:

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

Jeszcze raz dziękuję :)

Andre
źródło
Wygląda na to, że niektóre kolory nie mają 3 składników, na przykład UIColor.black.
Glenn Howes

Odpowiedzi:

75

W3C ma następujące elementy: http://www.w3.org/WAI/ER/WD-AERT/#color-contrast

Jeśli robisz tylko czarny lub biały tekst, użyj powyższego obliczenia jasności kolorów. Jeśli jest poniżej 125, użyj białego tekstu. Jeśli jest 125 lub więcej, użyj czarnego tekstu.

edycja 1: odchylenie w kierunku czarnego tekstu. :)

edycja 2: Formuła do użycia to ((wartość czerwona * 299) + (wartość zielona * 587) + (wartość niebieska * 114)) / 1000.

Erik Nedwidek
źródło
czy wiesz, jak uzyskać wartości koloru czerwonego, zielonego i niebieskiego?
Andre
Możesz użyć, NSArray *components = (NSArray *)CGColorGetComponents([UIColor CGColor]);aby uzyskać tablicę składników koloru, w tym alfa. Dokumenty nie określają, w jakiej kolejności się znajdują, ale zakładam, że byłby to czerwony, zielony, niebieski, alfa. Z dokumentów wynika również, że „rozmiar tablicy jest o jeden większy niż liczba składników przestrzeni kolorów dla koloru”. - nie mówi dlaczego ...
Jasarien
To dziwne ... używając: NSArray * components = (NSArray *) CGColorGetComponents (newColor.CGColor); NSLog (@ "moje komponenty koloru% f% f% f% f", komponenty [0], komponenty [1], komponenty [2], komponenty [3]); tylko po to, aby zobaczyć, czy mogę uzyskać wartości, wydaje się, że zmienia się tylko indeks 0, inne pozostaną takie same, niezależnie od wybranego koloru. Każdy pomysł, dlaczego?
Andre
Rozumiem. Nie możesz używać NSArray. Użyj tego zamiast tego: const CGFloat * components = CGColorGetComponents (newColor.CGColor); Kolejność jest również taka: czerwony to składniki [0]; zielony to komponenty [1]; niebieski to składniki [2]; alfa to składniki [3];
Andre
Ach, moja wina. Myślałem, że zwróciło CFArrayRef, a nie tablicę C. Przepraszam!
Jasarien
44

Oto rozszerzenie Swift (3) umożliwiające wykonanie tego sprawdzenia.

To rozszerzenie działa z kolorami w skali szarości. Jeśli jednak tworzysz wszystkie kolory za pomocą inicjatora RGB i nie używasz wbudowanych kolorów, takich jak UIColor.blacki UIColor.white, prawdopodobnie możesz usunąć dodatkowe kontrole.

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

Testy:

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

Uwaga: zaktualizowano do wersji Swift 3 12/7/18

josh-fuggle
źródło
6
Działa świetnie. W Swift 2.0 - wystąpił błąd kompilatora przy obliczaniu jasności: „Wyrażenie było zbyt złożone, aby można było je rozwiązać w rozsądnym czasie; rozważ podzielenie wyrażenia na odrębne wyrażenia podrzędne”. Naprawiłem to, dodając typ: "let jasność = (((components [0] * 299.0) as CGFloat) + ((components [1] * 587.0) as CGFloat) + ((components [2] * 114.0)) as CGFloat ) / (1000.0 jako CGFloat) ”
GK100
1
Miałem ten sam problem ze Swift 2.1. Rozwiązałem ten problem, po prostu tworząc zmienne dla komponentów, zamiast uzyskiwać do nich dostęp za pomocą components[0]. Tak jak to:let componentColorX: CGFloat = components[1]
petritz
1
Xcode mówi mi, że jasność = „Wyrażenie było zbyt skomplikowane, aby można było je rozwiązać w rozsądnym czasie; rozważ rozbicie wyrażenia na odrębne wyrażenia podrzędne”
daidai
daidai, po prostu uprość wyrażenie. Podział na końcu jest zbędny. Nie musimy pracować w zakresie 0 - 1. Nie dziel przez 1000, a zamiast tego sprawdź, czy wartość jest większa niż 500.
aronspring
32

Korzystając z odpowiedzi Erika Nedwidka, wymyśliłem mały fragment kodu do łatwego włączenia.

- (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
źródło
Użyłem tego kodu, działało idealnie, ale nie wiem, kiedy ustawię [UIColor blackColor], zwraca on darknessScore = 149,685. Czy możesz wyjaśnić, dlaczego tak się dzieje?
DivineDesert
3
Ten kod nie będzie działał z kolorami innymi niż RGB, takimi jak UIColor whiteColor, grayColor, blackColor.
rmaddy
@rmaddy, dlaczego tak jest? lub dlaczego nie ma kolorów whiteColor, grayColor i blackColor RGB?
mattsven
1
@rmaddy Jak programowo mogę odróżnić kolory w przestrzeni kolorów RGB od skali szarości?
mattsven
1
@maddy Nevermind! Dzięki za pomoc - 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
źródło
Dla mnie ten jest najlepszy. Możesz nawet ustawić zakres bieli w czeku tak, aby odpowiadał Twoim potrzebom i jest super prosty i natywny. Dzięki.
Andreas777
9

Wersja 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
źródło
1
To prawdopodobnie nie zadziała dla domyślnych kolorów systemowych, takich jak UIColor.white, ponieważ będzie miał tylko 2 komponenty, nie jest to reprezentacja RGB.
Skrew
7

Moje rozwiązanie tego problemu w kategorii (zaczerpnięte z innych odpowiedzi tutaj). Działa również z kolorami w skali szarości, których w chwili pisania tego tekstu żadna z pozostałych odpowiedzi nie działa.

@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
źródło
ładna obsługa kolorów innych niż RGB - działa jak urok.
hatunike
5

Prostsze rozszerzenie Swift 3:

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
źródło
2
To prawdopodobnie nie zadziała dla domyślnych kolorów systemowych, takich jak UIColor.white, ponieważ będzie miał tylko 2 komponenty, nie jest to reprezentacja RGB.
Skrew
Prawdziwe! Możesz przekonwertować kolor na ciąg szesnastkowy, a następnie z powrotem na UIColor, aby to uwzględnić.
Sunkas
3

Jeśli wolisz wersję blokową:

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
źródło
2

W przypadku wszystkiego, co nie jest szare, odwrotność RGB koloru jest zwykle silnie z nim kontrastowana. Demo po prostu odwraca kolor i usuwa jego nasycenie (zamienia go na szary).

Ale stworzenie ładnej kojącej kombinacji kolorów jest dość skomplikowane. Patrzeć na :

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

rep_movsd
źródło
1
Gdyby tylko była wersja na iPhone'a: ​​D
Andre
Więc jeśli mam wartości RGB koloru (których nie umiem zrobić) i utworzyłem nowy kolor z odwrotnością tych wartości, to powinno działać na bardzo podstawowym poziomie?
Andre
2

Następująca metoda polega na znalezieniu koloru jako jasnego lub ciemnego w języku Swift w oparciu o kolor biały.

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
}

Następująca metoda polega na znalezieniu koloru jako jasnego lub ciemnego w języku Swift przy użyciu składników koloru.

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
źródło
2

Dla mnie używanie tylko CGColorGetComponents nie zadziałało, otrzymuję 2 komponenty dla UIColors, takie jak biały. Muszę więc najpierw sprawdzić model przestrzeni kolorów. Oto, co wymyśliłem, i okazało się, że jest to szybka wersja odpowiedzi @ mattsven.

Przestrzeń kolorów pobrana stąd: 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
źródło
2

UIColor ma następującą metodę konwersji na przestrzeń kolorów HSB :

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

Dodana Swift 5wersja:

var white: CGFloat = 0.0
color.getWhite(&white, alpha: nil)
return white >= .85 // Don't use white background
Mike Glukhov
źródło
1
Działa to tylko wtedy, gdy UIColorjest to kolor w skali szarości. Losowy kolor RGB nie będzie działać z tym kodem.
rmaddy
0

Jeśli chcesz znaleźć jasność koloru, oto pseudo kod:

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

Jeśli jest> 0,5, oznacza to, że jest jasne, a poza tym ciemne.

Bryan Denny
źródło
2
Nie możesz jednakowo ważyć każdego z kolorów. Nasze oczy mają tendencję do niebieskiego i, w mniejszym stopniu, czerwonego.
Erik Nedwidek
@ErikNedwidek: W porządku, pytanie po prostu zadane o jasność koloru :)
Bryan Denny