比较两个 UIColors(点击 UIImageView 中的位置与资产目录颜色)

Posted

技术标签:

【中文标题】比较两个 UIColors(点击 UIImageView 中的位置与资产目录颜色)【英文标题】:Compare two UIColors (tap location in UIImageView vs assets catalog color) 【发布时间】:2020-05-21 19:41:26 【问题描述】:

我需要点击UIImageView ...

...并将点击位置的颜色与资产目录返回的颜色(或在代码中创建的自定义颜色)进行比较。

我在色彩空间方面遇到了很多问题,而且我的匹配项总是没有。我已经阅读了一些关于 *** 的优秀示例并尝试过,但我一定还是做错了。

我使用colorpicker from this answer Variations of these two Objective-C answers for matching

我也尝试过使用这样的自定义颜色(而不是资产颜色):

+ (UIColor *)themeRed 
    return [UIColor colorWithRed:192.0f/255.0f green:92.0f/255.0f blue:42.0f/255.0f alpha:1];

仍然没有匹配。我的测试匹配代码如下:

-(void)tappedColorView:(UITapGestureRecognizer *)tapRecognizer 
    CGPoint touchPoint = [tapRecognizer locationInView: uiiv_hs];
    //NSLog(@"my color %@", [uiiv_hs colorOfPoint:touchPoint]);

    UIColor *color = [uiiv_hs colorOfPoint:touchPoint];
    NSLog(@"color %@",[uiiv_hs colorOfPoint:touchPoint]);
    UIColor *matchcolor = [UIColor themeRed];
    NSLog(@"mcolor %@",[UIColor themeRed]);

    NSArray *colors = [NSArray arrayWithObjects:[UIColor colorNamed:@"Color01"],[UIColor colorNamed:@"Color02"], nil];

    if ([color matchesColor:matchcolor error:nil]) 
        NSLog(@"1Match!");
     else 
        NSLog(@"1No Match!");
    

    if ([color isEqualToColor:[UIColor themeRed]]) 
        NSLog(@"2Match!");
     else 
        NSLog(@"2No Match!");
    

【问题讨论】:

【参考方案1】:

如果您不熟悉以下主题,请不要这样做。我将向您展示一种方法,一种非常简单的方法,但会有一些问题。

资源

提前阅读或至少了解/熟悉它的东西。

颜色相关 Color space Color difference Color management(尤其是Color transformation) WWDC 2017 - 821 - Get Started with Display P3 浮点相关 What every computer scientist should know about floating-point arithmetic

问题 #1 - 你想要哪种颜色?

您的UIImageView 可以是完全不透明、透明、部分不透明、透明……假设UIImageView 下方有一个黄色视图,而UIImageView 不是不透明的,alpha 设置为50%。你想要什么颜色?原图颜色?渲染颜色(与黄色混合)?

我假设是与黄色混合的那个。这是获得正确颜色的代码。

目标-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

斯威夫特:

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)
    

这个函数应该返回一个颜色,其 alpha 分量设置为1.0。必须是1.0,否则我们无法继续。为什么?想象UIImageView alpha 设置为 50% -> 我们不会遍历视图层次结构(这个 targetOpaqueView 舞蹈)-> 我们将得到一个带有接近 0.5 的 alpha 分量的颜色 -> 没有任何用处,什么是以下?白色的?黑色的?橙色?

问题 #2 - 设备

每台设备都不同,可以显示不同的颜色范围 - iPad、iPhone……这还包括其他设备类型,例如电脑显示器、打印机……因为我不知道你想实现什么您的应用程序,将其作为提醒 - 颜色相同,但在每台设备上的外观不同。

问题 #3 - 颜色配置文件

这是 Display P3 配置文件和通用 sRGB 的比较。您只需在 macOS 上启动 ColorSync 实用程序即可比较更多配置文件。它表明当您将颜色从一个空间转换为另一个空间时,颜色会有所不同。

问题 #4 - 颜色转换

引自Color transformation章节:

颜色转换或颜色空间转换是一种颜色表示从一种颜色空间到另一种颜色空间的转换。

&

在几乎每一个翻译过程中,我们都必须处理不同设备的色域范围不同的事实,这使得准确的复制变得不可能。

这是一个复杂的过程,但它取决于很多事情(色彩空间,...)。它可能涉及分色(例如 16 位 -> 8 位),这取决于您的渲染意图(相对色度、感知等)等。

非常简化的示例 - 您有一个红色的图像 (#FF0000) 并且分配了通用 sRGB 配置文件。 iPhone 将显示它 (P3),您会看到 不同 颜色。更准确地说,如果您获得 RGB 分量值,它们会有所不同。

CoreGraphics.framework 提供颜色转换功能 - converted(to:intent:options:)。你必须通过:

目标色彩空间, intent = 当颜色超出新颜色空间的色域时用于匹配颜色的机制, 选项(只需传递nil)。

目标-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

斯威夫特:

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)
    

一个例子:

扩展 sRGB 颜色 (#CC3333) 的分量值 (RGBA):0.8 0.2 0.2 1 相同颜色转换为扩展线性 sRGB:0.603827 0.0331048 0.0331048 1 相同颜色转换为 Display P3:0.737027 0.252869 0.228974 1

问题 #5 - 自定义颜色

您可以通过两种方式创建颜色进行比较:

Xcode 资产目录 UIColor 初始化器

Xcode 资产目录允许您为不同的设备、色域指定颜色,或者您可以选择自定义内容(Display P3、sRGB、Extended Range sRGB、...)。

UIColor 初始化器允许您指定颜色(不仅仅是):

显示 P3 扩展范围 sRGB (ios >= 10)

请注意如何创建颜色进行比较。 RGB 分量值在各种色彩空间中有所不同。

问题 #6 - 颜色比较

正如您现在了解它的工作原理一样,您可以看到转换后的颜色无法精确匹配的方式涉及一些数学运算。我的意思是 - 两种颜色,转换为相同的颜色空间 - 你仍然不能用简单的相等运算符比较分量 (RGB)。由于转换,精度会丢失,即使不是 - 还记得What every computer scientist should know about floating-point arithmetic吗?

有一种方法可以实现您想要的,它被称为Color difference。换句话说 - 你想计算两种颜色的距离。

目标-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

斯威夫特:

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)
    

显示 P3:(0.692708 0.220536 0.201643 1) 显示 P3:(0.692708 0.220536 0.198637 1) 欧式距离:0.0030061900627510133

我们能够计算两种颜色的欧几里得距离,让我们编写一个简单的函数来检查两种颜色是否在一定的公差范围内匹配:

目标-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

斯威夫特:

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
    

所有的部分放在一起

我截取了你的图片 在 Pixelmator 中打开它并导出到网络(sRGB,没有关联的配置文件) 拖放到资产目录中 从图片中选择了红色 (#C02A2B) 是否将CustomRedColor 添加到资产目录中 色域任意,内容 sRGB,8 位十六进制 #C02A2B 利用我们讨论过的所有功能制作了一个带有轻按手势处理程序的简单视图控制器

目标-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

斯威夫特:

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
    

点击底部红色圆圈 我有Tapped color matches CustomRedColor: true

结论

正确设置颜色并不容易(定义、获取、比较、转换……)。试图在保持简单的同时指出重要的事情,但你现在应该能够做到。不要忘记正确创建颜色、转换为合适的颜色空间、选择适合您应用需求的容差等。

这里是 public GitHub Gist,它包含 Objective-C 和 Swift 视图控制器的实现。

附录

颜色空间转换不是必需的,因为[UIColor getRed:green:blue:alpha:] 文档说:

如果颜色在兼容的颜色空间中,则颜色将转换为 RGB 格式,并将其组件返回给您的应用程序。如果颜色不在兼容的颜色空间中,则参数不变。

&

red (green, blue) - 返回时,颜色对象的红色分量。在为 iOS 10 或更高版本链接的应用程序上,红色分量在扩展范围的 sRGB 颜色空间中指定,并且可以具有任何值。 0.0 到 1.0 之间的值在 sRGB 色域内。在早期版本的 iOS 上,指定的值始终介于 0.0 和 1.0 之间。

如果您使用此功能,应该为您转换颜色分量值。但我在这个答案中保留了转换代码,以演示幕后发生的事情。

【讨论】:

真正令人难以置信的答案。无与伦比的彻底性、全面性和具体示例。谢谢你。

以上是关于比较两个 UIColors(点击 UIImageView 中的位置与资产目录颜色)的主要内容,如果未能解决你的问题,请参考以下文章

在Swift中混合使用UIColors

怎样用Beyond Compare比较两个word文档的差异

比较两个网站之间的Google Analytics点击次数

如何使用wps比较两个文档的改动

Vscode并排窗口比较两个文件的不同

Excel文件比较工具的使用