Check if UIColor is dark or bright?

29,148

Solution 1

W3C has the following: http://www.w3.org/WAI/ER/WD-AERT/#color-contrast

If you're only doing black or white text, use the color brightness calculation above. If it is below 125, use white text. If it is 125 or above, use black text.

edit 1: bias towards black text. :)

edit 2: The formula to use is ((Red value * 299) + (Green value * 587) + (Blue value * 114)) / 1000.

Solution 2

Here is a Swift (3) extension to perform this check.

This extension works with greyscale colors. However, if you are creating all your colors with the RGB initializer and not using the built in colors such as UIColor.black and UIColor.white, then possibly you can remove the additional checks.

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

Note: Updated to Swift 3 12/7/18

Solution 3

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
}

Solution 4

Using Erik Nedwidek's answer, I came up with that little snippet of code for easy inclusion.

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

Solution 5

My solution to this problem in a category (drawn from other answers here). Also works with grayscale colors, which at the time of writing none of the other answers do.

@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
Share:
29,148
Andre
Author by

Andre

Updated on March 08, 2020

Comments

  • Andre
    Andre about 4 years

    I need to determine whether a selected UIColor (picked by the user) is dark or bright, so I can change the color of a line of text that sits on top of that color, for better readability.

    Here's an example in Flash/Actionscript (with demo): http://web.archive.org/web/20100102024448/http://theflashblog.com/?p=173

    Any thoughts?

    Cheers, Andre

    UPDATE

    Thanks to everyone's suggestions, here's the working code:

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

    Thanks once again :)

  • Erik Nedwidek
    Erik Nedwidek about 14 years
    You can't weight each of the colors equally. Our eyes have a bias against blue and to a lesser extent red.
  • Andre
    Andre about 14 years
    If only there was an iPhone version of that :D
  • Andre
    Andre about 14 years
    So if I got the RGB values of a color (which I don't know how to do) and created a new color with the inverse of those values, that should work on a very basic level?
  • Andre
    Andre about 14 years
    do you know how to get the red , green and blue values of a color?
  • Jasarien
    Jasarien about 14 years
    You can use NSArray *components = (NSArray *)CGColorGetComponents([UIColor CGColor]); to get an array of the colour components, including the alpha. The docs don't specify what order they're in but I assume it would be red, green, blue, alpha. Also, from the docs, "the size of the array is one more than the number of components of the color space for the color." - it doesn't say why...
  • Andre
    Andre about 14 years
    That's odd...using: NSArray *components = (NSArray *) CGColorGetComponents(newColor.CGColor); NSLog(@"my color components %f %f %f %f", components[0], components[1], components[2], components[3]); just to see if i can get the values, it seems only the 0 index changes, the others will remain the same, regardless of what color I pick. Any idea why?
  • Andre
    Andre about 14 years
    Got it. You can't use NSArray. Use this instead: const CGFloat *components = CGColorGetComponents(newColor.CGColor); Also, the order is: red is components[0]; green is components[1]; blue is components[2]; alpha is components[3];
  • Jasarien
    Jasarien about 14 years
    Ah, my bad. I thought it returned a CFArrayRef, not a C array. Sorry!
  • Bryan Denny
    Bryan Denny about 14 years
    @ErikNedwidek: Fair enough, the question simply asked for the brightness of a color :)
  • DivineDesert
    DivineDesert almost 10 years
    I used this code, worked perfectly, but dont know when I set [UIColor blackColor], it returns darknessScore = 149.685. Can you explain why this is happening ?
  • rmaddy
    rmaddy over 9 years
    This only works if the UIColor is a grayscale color. A random RGB color won't work with this code.
  • rmaddy
    rmaddy over 9 years
    This code won't work with non-RGB colors such as UIColor whiteColor, grayColor, blackColor.
  • mattsven
    mattsven about 9 years
    @rmaddy why is that? or why aren't whiteColor, grayColor and blackColor RGB colors?
  • rmaddy
    rmaddy about 9 years
    @mattsven Because those colors are gray scale colors and they only have one color component.
  • mattsven
    mattsven about 9 years
    @rmaddy How can I programmatically tell the difference between colors in the RGB color space vs. grayscale?
  • mattsven
    mattsven about 9 years
    @maddy Nevermind! Thanks for the help - stackoverflow.com/questions/1099569/…
  • hatunike
    hatunike almost 9 years
    nice handling of non RGB colors - works like a charm.
  • GK100
    GK100 over 8 years
    Works Great. With Swift 2.0 - I got a compiler error for the brightness calculation: "Expression was too complex to be solved in reasonable time; consider breaking up the expression into distinct sub-expressions". I fixed it by adding type: "let brightness = (((components[0] * 299.0) as CGFloat) + ((components[1] * 587.0) as CGFloat) + ((components[2] * 114.0)) as CGFloat) / (1000.0 as CGFloat)"
  • petritz
    petritz over 8 years
    I had the same issue with Swift 2.1. I solved this issue by simply creating variables for the components instead of accessing them with components[0]. Like so: let componentColorX: CGFloat = components[1]
  • daihovey
    daihovey about 8 years
    Xcode tells me the brightness = "Expression was too complex to be solved in reasonable time; consider breaking up the expression into distinct sub-expressions"
  • aronspring
    aronspring about 8 years
    daidai, just simplify the expression. The division at the end is unnecessary. We don't need to work in the range 0 - 1. Don't divide by 1000, then just check if the value is greater than 500 instead.
  • Andreas777
    Andreas777 almost 7 years
    For me this one is the best. You can even set range of white in the check to suit your needs and is super simple and native. Thanks.
  • Skrew
    Skrew almost 6 years
    That probably won't work for default system colors like UIColor.white as it'll have only 2 components, it's not RGB representation.
  • Skrew
    Skrew almost 6 years
    That probably won't work for default system colors like UIColor.white as it'll have only 2 components, it's not RGB representation.
  • Sunkas
    Sunkas over 5 years
    True! You could convert the color to hex-string and then back to UIColor to account for that.
  • TonnyTao
    TonnyTao about 3 years
    ((rgb.red * 299) + (rgb.green * 587) + (rgb.blue * 114)) / 1000 is more accurate, e.g. for color #7473E8, getWhite(&white, alpha: nil) is 0.49656128883361816, but this formula is 0.5044464148879051, so the formula way is better than getWhite.