How to Compare Uicolors

How to compare UIColors?

Have you tried [myColor isEqual:someOtherColor] ?

How to compare UIColors in Swift

The solution I needed:

 func compareColors (c1:UIColor, c2:UIColor) -> Bool{
// some kind of weird rounding made the colors unequal so had to compare like this

var red:CGFloat = 0
var green:CGFloat = 0
var blue:CGFloat = 0
var alpha:CGFloat = 0
c1.getRed(&red, green: &green, blue: &blue, alpha: &alpha)

var red2:CGFloat = 0
var green2:CGFloat = 0
var blue2:CGFloat = 0
var alpha2:CGFloat = 0
c2.getRed(&red2, green: &green2, blue: &blue2, alpha: &alpha2)

return return (Int(red*255) == Int(red*255) && Int(green*255) == Int(green2*255) && Int(blue*255) == Int(blue*255) )


}

Comparing 2 UIColor(s)

If you are creating all color the same way you can just use ==.

If your colors are in different color spaces and you just want to compare their RGBA value use the following:

extension UIColor {
func equals(_ rhs: UIColor) -> Bool {
var lhsR: CGFloat = 0
var lhsG: CGFloat = 0
var lhsB: CGFloat = 0
var lhsA: CGFloat = 0
self.getRed(&lhsR, green: &lhsG, blue: &lhsB, alpha: &lhsA)

var rhsR: CGFloat = 0
var rhsG: CGFloat = 0
var rhsB: CGFloat = 0
var rhsA: CGFloat = 0
rhs.getRed(&rhsR, green: &rhsG, blue: &rhsB, alpha: &rhsA)

return lhsR == rhsR &&
lhsG == rhsG &&
lhsB == rhsB &&
lhsA == rhsA
}
}

For instance:

let white1 = UIColor.white
let white2 = UIColor(colorLiteralRed: 1, green: 1, blue: 1, alpha: 1)
white1 == white2 //false
white1.cgColor == white2.cgColor //false
white1.equals(white2) //true

How to compare a color in swift

Try CGColorEqualToColor(_ color1: CGColor!, _ color2: CGColor!) -> Bool.

How to compare colors in swift

You don't compare colors using the == operator. You do it like this and you need the ! to unwrap the optional color:

if sender.backgroundColor!.isEqual(UIColor.redColor()) {
            
}

Also, remove the extraneous = in your assignment statement. It should be:

sender.backgroundColor = UIColor.whiteColor()

How to compare two UIColor which have almost same shade or range in iOS?

You need a tolerance, the value of which, only you can decide:

- (BOOL)color:(UIColor *)color1
isEqualToColor:(UIColor *)color2
withTolerance:(CGFloat)tolerance {

CGFloat r1, g1, b1, a1, r2, g2, b2, a2;
[color1 getRed:&r1 green:&g1 blue:&b1 alpha:&a1];
[color2 getRed:&r2 green:&g2 blue:&b2 alpha:&a2];
return
fabs(r1 - r2) <= tolerance &&
fabs(g1 - g2) <= tolerance &&
fabs(b1 - b2) <= tolerance &&
fabs(a1 - a2) <= tolerance;
}

...

UIColor *color1 = [UIColor colorWithRed:1 green:(CGFloat)0.4 blue:1 alpha:1];
UIColor *color2 = [UIColor colorWithRed:1 green:(CGFloat)0.2 blue:1 alpha:1];

if ([self color:color1 isEqualToColor:color2 withTolerance:0.2]) {
NSLog(@"equals");
} else {
NSLog(@"not equal");
}

Comparison of UIColors

gist.github.com/mtabini/716917

The link above works like a charm. Thanks awfully "Count Chocula" both a delicious source of chocolate and iphone answers :P

I'd click answered to your post but it would more then likly misslead any people wondering on here.

Must have just been a float tolerance.

Compare two UIColors (tap location in UIImageView vs assets catalog color)

Don't do this if you're not familiar with the following topics. I'm going to show you one way, a very simplified one, but there will be gotchas.

Resources

Something to read upfront or at least be aware/familiar with it.

  • Color related

    • Color space
    • Color difference
    • Color management (especially Color transformation)
    • WWDC 2017 - 821 - Get Started with Display P3
  • Float related

    • What every computer scientist should know about floating-point arithmetic

Problem #1 - Which color do you want?

Your UIImageView can be fully opaque, transparent, partially opaque, transparent, ... Let's say that there's a view with yellow color below the UIImageView and the UIImageView isn't opaque and alpha is set to 50%. What color do you want? Original image color? Rendered color (mixed with the yellow one)?

Comparison of colors from UIView with different alpha channel

I assume the one mixed with the yellow. Here's the code to get the right color.

Objective-C:

@interface UIView(ColorAtPoint)

- (UIColor *)colorAt:(CGPoint)point;

@end

@implementation UIView(ColorAtPoint)

- (UIColor *)colorAt:(CGPoint)point {
UIView *targetOpaqueView = self;

while (!targetOpaqueView.isOpaque && targetOpaqueView.superview != nil) {
targetOpaqueView = targetOpaqueView.superview;
}

CGPoint targetPoint = [self convertPoint:point toView:targetOpaqueView];

unsigned char pixel[4] = {0};
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
if (colorSpace == NULL) {
return nil;
}

CGContextRef context = CGBitmapContextCreate(pixel, 1, 1, 8, 4, colorSpace, (CGBitmapInfo)kCGImageAlphaPremultipliedLast);
if (context == NULL) {
CGColorSpaceRelease(colorSpace);
return nil;
}

CGContextTranslateCTM(context, -targetPoint.x, -targetPoint.y);
[targetOpaqueView.layer renderInContext:context];

CGContextRelease(context);
CGColorSpaceRelease(colorSpace);

UIColor *color = [UIColor colorWithRed:pixel[0]/255.0
green:pixel[1]/255.0
blue:pixel[2]/255.0
alpha:pixel[3]/255.0];

return color;
}

@end

Swift:

extension UIView {
func color(at point: CGPoint) -> UIColor? {
var targetOpaqueView: UIView = self

// Traverse the view hierarchy to find a parent which is opaque
while !targetOpaqueView.isOpaque && targetOpaqueView.superview != nil {
targetOpaqueView = targetOpaqueView.superview!
}

// Convert the point from our view to the target one
let targetPoint: CGPoint = convert(point, to: targetOpaqueView)

let colorSpace = CGColorSpaceCreateDeviceRGB()
let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue)
var pixel: [UInt8] = [0, 0, 0, 0]

guard let context = CGContext(data: &pixel, width: 1, height: 1, bitsPerComponent: 8, bytesPerRow: 4, space: colorSpace, bitmapInfo: bitmapInfo.rawValue) else {
return nil
}

context.translateBy(x: -targetPoint.x, y: -targetPoint.y)

// Render the target opaque view to get all the possible transparency right
targetOpaqueView.layer.render(in: context)
return UIColor(red: CGFloat(pixel[0])/255.0, green: CGFloat(pixel[1])/255.0, blue: CGFloat(pixel[2])/255.0, alpha: CGFloat(pixel[3])/255.0)
}
}

This function should return a color with alpha component set to 1.0. It has to be 1.0 otherwise we can't continue. Why? Imagine that the UIImageView alpha is set to 50% -> we wont traverse the view hierarchy (this targetOpaqueView dance) -> we will get a color with alpha component close to 0.5 -> not of any use, what's below? White? Black? Orange?

Problem #2 - Device

Every device is different and can display different range of colors - iPads, iPhones, ... This also includes other device types like computer displays, printers, ... Because I don't know what are you trying to achieve in your application, take it as a reminder - same color, but different look on every device.

Problem #3 - Color profile

Here's the comparison of the Display P3 profile and Generic sRGB. You can compare more profiles just by launching ColorSync Utility on your macOS. It demonstrates that the color will differ when you convert a color from one space to another one.

Display P3 and Generic sRGBP comparison

Problem #4 - Color conversion

Quotes from the Color transformation chapter:

Color transformation, or color space conversion, is the transformation of the representation of a color from one color space to another.

&

In nearly every translation process, we have to deal with the fact that the color gamut of different devices vary in range which makes an accurate reproduction impossible.

This is sort of complicated process, but it depends on many things (color spaces, ...). It can involve posterization (16-bit -> 8-bit for example), it depends on your rendering intent (relative colorimetric, perceptual, ...), etc.

Very simplified example - you have an image with red color (#FF0000) and there's Generic sRGB profile assigned to it. iPhone is going to display it (P3) and you'll see different color. More precisely, if you get RGB component values, they'll differ.

CoreGraphics.framework provides color conversion function - converted(to:intent:options:). You have to pass:

  • the destination color space,
  • intent = the mechanism to use to match the color when the color is outside the gamut of the new color space,
  • options (just pass nil).

Objective-C:

@interface UIColor(ColorSpaceConversion)

- (UIColor *)convertedToColorSpace:(CGColorSpaceRef)colorSpace;
- (UIColor *)convertedToColorSpace:(CGColorSpaceRef)colorSpace inten:(CGColorRenderingIntent)intent;

@end

@implementation UIColor(ColorSpaceConversion)

- (UIColor *)convertedToColorSpace:(CGColorSpaceRef)colorSpace {
return [self convertedToColorSpace:colorSpace inten:kCGRenderingIntentDefault];
}

- (UIColor *)convertedToColorSpace:(CGColorSpaceRef)colorSpace inten:(CGColorRenderingIntent)intent {
CGColorRef converted = CGColorCreateCopyByMatchingToColorSpace(colorSpace, intent, self.CGColor, NULL);

if (converted == NULL) {
return nil;
}

UIColor *color = [[UIColor alloc] initWithCGColor:converted];
CGColorRelease(converted);
return color;
}

@end

Swift:

extension UIColor {
func converted(toColorSpace colorSpace: CGColorSpace, intent: CGColorRenderingIntent = .defaultIntent) -> UIColor? {
guard let converted = cgColor.converted(to: colorSpace, intent: intent, options: nil) else {
return nil
}

return UIColor(cgColor: converted)
}
}

An example:

  • Component values (RGBA) of the Extended sRGB color (#CC3333): 0.8 0.2 0.2 1
  • Same color converted to Extended Linear sRGB: 0.603827 0.0331048 0.0331048 1
  • Same color converted to Display P3: 0.737027 0.252869 0.228974 1

Problem #5 - Custom color

You can create colors to compare against in two ways:

  • Xcode assets catalog
  • UIColor initializer

Xcode assets catalog allows you to specify color for different devices, gamuts or you can select custom content (Display P3, sRGB, Extended Range sRGB, ...).

UIColor initializer allows you to specify color in (not just):

  • Display P3
  • Extended Range sRGB (iOS >= 10)

Be aware how do you create a color to compare against. RGB component values differ in various color spaces.

Problem #6 - Color comparison

As you are aware of how it works now, you can see that there's some math involved in a way that the converted color won't precisely match. I mean - two colors, converted to the same color space - you can't still compare components (RGB) with simple equality operator. Precision is lost due to conversion and even if not - remember What every computer scientist should know about floating-point arithmetic?

There's a way how to achieve what you want and it's called Color difference. In other words - you'd like to calculate two colors distance.

Objective-C:

@interface UIColor(EuclideanDistance)

- (CGFloat)euclideanDistanceTo:(UIColor *)other;

@end

@implementation UIColor(EuclideanDistance)

- (CGFloat)euclideanDistanceTo:(UIColor *)other {
CIColor *ciColor = [[CIColor alloc] initWithColor:self];
CIColor *ciColorOther = [[CIColor alloc] initWithColor:other];

if (ciColor.numberOfComponents != ciColor.numberOfComponents) {
NSException *exception = [NSException exceptionWithName:NSInvalidArgumentException
reason:@"Colors differ in numberOfComponents"
userInfo:nil];
@throw exception;
}

if (ciColor.alpha != 1.0 || ciColorOther.alpha != 1.0) {
NSException *exception = [NSException exceptionWithName:NSInvalidArgumentException
reason:@"Transparent colors are not supported"
userInfo:nil];
@throw exception;
}

CGFloat dr = ciColorOther.red - ciColor.red;
CGFloat dg = ciColorOther.green - ciColor.green;
CGFloat db = ciColorOther.blue - ciColor.blue;

return sqrt(dr * dr + dg * dg + db * db);
}

@end

Swift:

extension UIColor {
func euclideanDistance(to other: UIColor) -> CGFloat? {
let ciColor = CIColor(color: self)
let ciColorOther = CIColor(color: other)

guard ciColor.numberOfComponents == ciColorOther.numberOfComponents,
ciColor.alpha == 1.0, ciColorOther.alpha == 1.0 else {
return nil;
}

let dr = ciColorOther.red - ciColor.red
let dg = ciColorOther.green - ciColor.green
let db = ciColorOther.blue - ciColor.blue

return sqrt(dr * dr + dg * dg + db * db)
}
}
  • Display P3: (0.692708 0.220536 0.201643 1)
  • Display P3: (0.692708 0.220536 0.198637 1)
  • Euclidean distance: 0.0030061900627510133

We're able to calculate Euclidean distance of two colors, let's write a simple function to check if two colors matches with some tolerance:

Objective-C:

@interface UIColor(Matching)

- (BOOL)matchesTo:(UIColor *)other;
- (BOOL)matchesTo:(UIColor *)other euclideanDistanceTolerance:(CGFloat)tolerance;

@end

@implementation UIColor(Matching)

- (BOOL)matchesTo:(UIColor *)other {
return [self matchesTo:other euclideanDistanceTolerance:0.01];
}

- (BOOL)matchesTo:(UIColor *)other euclideanDistanceTolerance:(CGFloat)tolerance {
CGFloat distance = [self euclideanDistanceTo:other];
return distance <= tolerance;
}

@end

Swift:

extension UIColor {
func matches(to other: UIColor, euclideanDistanceTolerance tolerance: CGFloat = 0.01) -> Bool? {
guard let distance = euclideanDistance(to: other) else {
return nil
}

return distance <= tolerance
}
}

All the pieces together

  • I took a screenshot of your image
  • Did open it in the Pixelmator and exported for web (sRGB, no profile associated)
  • Dragged & dropped into the assets catalog
  • Picked the red color (#C02A2B) from the image
  • Did add CustomRedColor into the assets catalog

    • Gamut Any, Content sRGB, 8-bit Hexadecimal #C02A2B
  • Made a simple view controller with tap gesture handler utilizing all the functions we have discussed

Objective-C:

@interface ViewController ()

@property (nonatomic, strong) IBOutlet UIImageView *imageView;
@property (nonatomic, strong) IBOutlet UIView *leftView;
@property (nonatomic, strong) IBOutlet UIView *rightView;

@end

@implementation ViewController

- (IBAction)handleTapGesture:(UITapGestureRecognizer *)sender {
CGPoint location = [sender locationInView:self.imageView];

CGColorSpaceRef colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceDisplayP3);
if (colorSpace == NULL) {
return;
}

UIColor *assetsCatalogRedColor = [UIColor colorNamed:@"CustomRedColor"];
UIColor *redColor = [assetsCatalogRedColor convertedToColorSpace:colorSpace];
UIColor *tappedColor = [[self.imageView colorAt:location] convertedToColorSpace:colorSpace];

CGColorSpaceRelease(colorSpace);

self.leftView.backgroundColor = tappedColor;
self.rightView.backgroundColor = redColor;

if (redColor == nil || tappedColor == nil) {
return;
}

@try {
BOOL matches = [tappedColor matchesTo:redColor];
NSLog(@"Tapped color matches CustomRedColor: %@", matches ? @"true" : @"false");
}
@catch (NSException *exception) {
NSLog(@"Something went wrong: %@", exception);
}
}

@end

Swift:

class ViewController: UIViewController {
@IBOutlet var imageView: UIImageView!
@IBOutlet var leftView: UIView!
@IBOutlet var rightView: UIView!

private let assetsCatalogRedColor: UIColor = UIColor(named: "CustomRedColor")!

@IBAction
func handleTap(sender: UITapGestureRecognizer) {
let location = sender.location(in: imageView)

guard let colorSpace = CGColorSpace(name: CGColorSpace.displayP3),
let redColor = assetsCatalogRedColor.converted(toColorSpace: colorSpace, intent: .defaultIntent),
let tappedColor = imageView.color(at: location)?.converted(toColorSpace: colorSpace, intent: .defaultIntent) else {
return
}

let matches = tappedColor.matches(to: redColor) ?? false

print("Tapped color matches CustomRedColor: \(matches)")

leftView.backgroundColor = tappedColor
rightView.backgroundColor = redColor
}
}

Sample Image

  • Tapped the bottom red circle
  • And I've got Tapped color matches CustomRedColor: true

Conclusion

It's not that easy to get colors right (define, get, compare, convert, ...). Tried to point out important things while keeping it simple, but you should be able to do it now. Don't forget to correctly create a color, convert to a proper color space, pick a tolerance which suits your application needs, etc.

Here's the public GitHub Gist which contains both Objective-C & Swift view controller implementations.

Addendum

The color space conversion is not necessary, because the [UIColor getRed:green:blue:alpha:] documentation says:

If the color is in a compatible color space, the color is converted into RGB format and its components are returned to your application. If the color is not in a compatible color space, the parameters are unchanged.

&

red (green, blue) - On return, the red component of the color object. On applications linked for iOS 10 or later, the red component is specified in an extended range sRGB color space and can have any value. Values between 0.0 and 1.0 are inside the sRGB color gamut. On earlier versions of iOS, the specified value is always between 0.0 and 1.0.

Color component values should be converted for you if you use this function. But I kept the conversion code in this answer to demonstrate what's going on under the hood.



Related Topics



Leave a reply



Submit