Check if UIColor is dark or bright?
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
Andre
Updated on March 08, 2020Comments
-
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 about 14 yearsYou can't weight each of the colors equally. Our eyes have a bias against blue and to a lesser extent red.
-
Andre about 14 yearsIf only there was an iPhone version of that :D
-
Andre about 14 yearsSo 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 about 14 yearsdo you know how to get the red , green and blue values of a color?
-
Jasarien about 14 yearsYou 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 about 14 yearsThat'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 about 14 yearsGot 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 about 14 yearsAh, my bad. I thought it returned a CFArrayRef, not a C array. Sorry!
-
Bryan Denny about 14 years@ErikNedwidek: Fair enough, the question simply asked for the brightness of a color :)
-
DivineDesert almost 10 yearsI 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 over 9 yearsThis only works if the
UIColor
is a grayscale color. A random RGB color won't work with this code. -
rmaddy over 9 yearsThis code won't work with non-RGB colors such as
UIColor whiteColor, grayColor, blackColor
. -
mattsven about 9 years@rmaddy why is that? or why aren't whiteColor, grayColor and blackColor RGB colors?
-
rmaddy about 9 years@mattsven Because those colors are gray scale colors and they only have one color component.
-
mattsven about 9 years@rmaddy How can I programmatically tell the difference between colors in the RGB color space vs. grayscale?
-
mattsven about 9 years@maddy Nevermind! Thanks for the help - stackoverflow.com/questions/1099569/…
-
hatunike almost 9 yearsnice handling of non RGB colors - works like a charm.
-
GK100 over 8 yearsWorks 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 over 8 yearsI 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 about 8 yearsXcode tells me the brightness = "Expression was too complex to be solved in reasonable time; consider breaking up the expression into distinct sub-expressions"
-
aronspring about 8 yearsdaidai, 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 almost 7 yearsFor 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 almost 6 yearsThat probably won't work for default system colors like UIColor.white as it'll have only 2 components, it's not RGB representation.
-
Skrew almost 6 yearsThat probably won't work for default system colors like UIColor.white as it'll have only 2 components, it's not RGB representation.
-
Sunkas over 5 yearsTrue! You could convert the color to hex-string and then back to UIColor to account for that.
-
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.