比较两个 UIColors(UIImageView 中的点击位置与资产目录颜色)
Compare two UIColors (tap location in UIImageView vs assets catalog color)
我需要点击 UIImageView
...
...并将点击位置的颜色与资产目录返回的颜色(或在代码中创建的自定义颜色)进行比较。
我在色彩空间方面遇到了很多麻烦,而且我的比赛总是没有。我在 Whosebug 上阅读了一些很好的示例并尝试了它们,但我一定还是做错了什么。
- 我用的是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!");
}
}
如果您不熟悉以下主题,请不要这样做。我将向您展示一种方法,一种非常简单的方法,但会有陷阱。
资源
需要预先阅读的东西,或者至少 aware/familiar 阅读它。
- 颜色相关
- 浮动相关
问题 #1 - 你想要哪种颜色?
您的 UIImageView
可以是完全不透明的、透明的、部分不透明的、透明的……假设 UIImageView
下面有一个黄色视图,而 UIImageView
不是't 不透明且 alpha 设置为 50%。你想要什么颜色?原图颜色?渲染颜色(与黄色混合)?
我假设是混合了黄色的那个。这是获得正确颜色的代码。
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)
}
}
此函数应 return 一种颜色,其 alpha 分量设置为 1.0
。它必须是 1.0
否则我们无法继续。为什么?假设 UIImageView
alpha 设置为 50% -> 我们不会遍历视图层次结构(这个 targetOpaqueView
舞蹈) -> 我们将得到一个 alpha 分量接近 0.5
的颜色 ->没有任何用处,下面是什么?白色的?黑色的?橙色?
问题 #2 - 设备
每个设备都不同,可以显示不同范围的颜色 - iPad、iPhones、...这还包括其他设备类型,如计算机显示器、打印机...因为我不知道你想在你的应用程序中实现什么,把它作为一个提醒 - 相同的颜色,但每个设备上的外观不同。
问题 #3 - 颜色配置文件
这是 Display P3 配置文件和通用 sRGB 的比较。您只需在 macOS 上启动 ColorSync Utility 即可比较更多配置文件。它表明当您将一种颜色 space 转换为另一种颜色时,颜色会有所不同。
问题 #4 - 颜色转换
引自 Color transformation 章节:
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.
这是一个复杂的过程,但它取决于很多因素(颜色 spaces,...)。它可能涉及分色(例如 16 位 -> 8 位),这取决于您的渲染意图(相对色度、感知等)等。
非常简单的示例 - 您有一张红色图像 (#FF0000
),并且为其分配了通用 sRGB 配置文件。 iPhone 将显示它 (P3),您会看到 不同的 颜色。更准确地说,如果您获得 RGB 分量值,它们会有所不同。
CoreGraphics.framework提供颜色转换功能-converted(to:intent:options:)
。您必须通过:
- 目标颜色space,
- intent = 当颜色超出新颜色的色域时用于匹配颜色的机制 space,
- 选项(只需传递
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)
}
}
一个例子:
- 扩展 sRGB 颜色 (
#CC3333
) 的分量值 (RGBA):0.8 0.2 0.2 1
- 相同颜色转换为扩展线性 sRGB:0.603827 0.0331048 0.0331048 1
- 相同颜色转换为显示 P3:0.737027 0.252869 0.228974 1
问题 #5 - 自定义颜色
您可以通过两种方式创建要比较的颜色:
- Xcode 资产目录
UIColor
初始化器
Xcode 资产目录允许您为不同的设备、色域指定颜色,或者您可以 select 自定义内容(显示 P3、sRGB、扩展范围 sRGB,...)。
UIColor
初始化程序允许您指定颜色(不仅仅是):
- 显示P3
- 扩展范围 sRGB (iOS >= 10)
注意如何创建颜色进行比较。各种颜色的RGB分量值不同spaces.
问题 #6 - 颜色比较
正如您现在了解它的工作原理一样,您可以看到转换后的颜色不会精确匹配的方式涉及一些数学。我的意思是 - 两种颜色,转换为相同的颜色 space - 你仍然不能将组件 (RGB) 与简单的相等运算符进行比较。精度因转换而丢失,即使不是 - 记住 What every computer scientist should know about floating-point arithmetic?
有一种方法可以实现您想要的,它被称为 Color difference。换句话说 - 你想计算两种颜色的距离。
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)
}
}
- 显示 P3:(0.692708 0.220536 0.201643 1)
- 显示 P3:(0.692708 0.220536 0.198637 1)
- 欧氏距离:0.0030061900627510133
我们能够计算两种颜色的欧几里得距离,让我们编写一个简单的函数来检查两种颜色是否匹配某种公差:
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
}
}
所有的碎片一起
- 我给你截图了
- 在 Pixelmator 中打开它并导出到网络(sRGB,没有关联的配置文件)
- 拖放到资产目录中
- 从图像中选取红色 (
#C02A2B
)
- 是否将
CustomRedColor
添加到资产目录中
- 任意色域,内容 sRGB,8 位十六进制
#C02A2B
- 利用我们讨论过的所有功能制作了一个带有点击手势处理程序的简单视图控制器
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
}
}
- 点击底部红圈
- 我有
Tapped color matches CustomRedColor: true
结论
获得正确的颜色(定义、获取、比较、转换...)并不是那么容易。试图在保持简单的同时指出重要的事情,但你现在应该能够做到。不要忘记正确创建颜色、转换为合适的颜色 space、选择适合您应用需求的公差等。
这是 public GitHub Gist,其中包含 Objective-C 和 Swift 视图控制器实现。
附录
不需要颜色 space 转换,因为 [UIColor getRed:green:blue:alpha:]
文档说:
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.
如果您使用此功能,应该为您转换颜色分量值。但我在这个答案中保留了转换代码,以演示幕后发生的事情。
我需要点击 UIImageView
...
...并将点击位置的颜色与资产目录返回的颜色(或在代码中创建的自定义颜色)进行比较。
我在色彩空间方面遇到了很多麻烦,而且我的比赛总是没有。我在 Whosebug 上阅读了一些很好的示例并尝试了它们,但我一定还是做错了什么。
- 我用的是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!");
}
}
如果您不熟悉以下主题,请不要这样做。我将向您展示一种方法,一种非常简单的方法,但会有陷阱。
资源
需要预先阅读的东西,或者至少 aware/familiar 阅读它。
- 颜色相关
- 浮动相关
问题 #1 - 你想要哪种颜色?
您的 UIImageView
可以是完全不透明的、透明的、部分不透明的、透明的……假设 UIImageView
下面有一个黄色视图,而 UIImageView
不是't 不透明且 alpha 设置为 50%。你想要什么颜色?原图颜色?渲染颜色(与黄色混合)?
我假设是混合了黄色的那个。这是获得正确颜色的代码。
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)
}
}
此函数应 return 一种颜色,其 alpha 分量设置为 1.0
。它必须是 1.0
否则我们无法继续。为什么?假设 UIImageView
alpha 设置为 50% -> 我们不会遍历视图层次结构(这个 targetOpaqueView
舞蹈) -> 我们将得到一个 alpha 分量接近 0.5
的颜色 ->没有任何用处,下面是什么?白色的?黑色的?橙色?
问题 #2 - 设备
每个设备都不同,可以显示不同范围的颜色 - iPad、iPhones、...这还包括其他设备类型,如计算机显示器、打印机...因为我不知道你想在你的应用程序中实现什么,把它作为一个提醒 - 相同的颜色,但每个设备上的外观不同。
问题 #3 - 颜色配置文件
这是 Display P3 配置文件和通用 sRGB 的比较。您只需在 macOS 上启动 ColorSync Utility 即可比较更多配置文件。它表明当您将一种颜色 space 转换为另一种颜色时,颜色会有所不同。
问题 #4 - 颜色转换
引自 Color transformation 章节:
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.
这是一个复杂的过程,但它取决于很多因素(颜色 spaces,...)。它可能涉及分色(例如 16 位 -> 8 位),这取决于您的渲染意图(相对色度、感知等)等。
非常简单的示例 - 您有一张红色图像 (#FF0000
),并且为其分配了通用 sRGB 配置文件。 iPhone 将显示它 (P3),您会看到 不同的 颜色。更准确地说,如果您获得 RGB 分量值,它们会有所不同。
CoreGraphics.framework提供颜色转换功能-converted(to:intent:options:)
。您必须通过:
- 目标颜色space,
- intent = 当颜色超出新颜色的色域时用于匹配颜色的机制 space,
- 选项(只需传递
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)
}
}
一个例子:
- 扩展 sRGB 颜色 (
#CC3333
) 的分量值 (RGBA):0.8 0.2 0.2 1 - 相同颜色转换为扩展线性 sRGB:0.603827 0.0331048 0.0331048 1
- 相同颜色转换为显示 P3:0.737027 0.252869 0.228974 1
问题 #5 - 自定义颜色
您可以通过两种方式创建要比较的颜色:
- Xcode 资产目录
UIColor
初始化器
Xcode 资产目录允许您为不同的设备、色域指定颜色,或者您可以 select 自定义内容(显示 P3、sRGB、扩展范围 sRGB,...)。
UIColor
初始化程序允许您指定颜色(不仅仅是):
- 显示P3
- 扩展范围 sRGB (iOS >= 10)
注意如何创建颜色进行比较。各种颜色的RGB分量值不同spaces.
问题 #6 - 颜色比较
正如您现在了解它的工作原理一样,您可以看到转换后的颜色不会精确匹配的方式涉及一些数学。我的意思是 - 两种颜色,转换为相同的颜色 space - 你仍然不能将组件 (RGB) 与简单的相等运算符进行比较。精度因转换而丢失,即使不是 - 记住 What every computer scientist should know about floating-point arithmetic?
有一种方法可以实现您想要的,它被称为 Color difference。换句话说 - 你想计算两种颜色的距离。
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)
}
}
- 显示 P3:(0.692708 0.220536 0.201643 1)
- 显示 P3:(0.692708 0.220536 0.198637 1)
- 欧氏距离:0.0030061900627510133
我们能够计算两种颜色的欧几里得距离,让我们编写一个简单的函数来检查两种颜色是否匹配某种公差:
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
}
}
所有的碎片一起
- 我给你截图了
- 在 Pixelmator 中打开它并导出到网络(sRGB,没有关联的配置文件)
- 拖放到资产目录中
- 从图像中选取红色 (
#C02A2B
) - 是否将
CustomRedColor
添加到资产目录中- 任意色域,内容 sRGB,8 位十六进制
#C02A2B
- 任意色域,内容 sRGB,8 位十六进制
- 利用我们讨论过的所有功能制作了一个带有点击手势处理程序的简单视图控制器
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
}
}
- 点击底部红圈
- 我有
Tapped color matches CustomRedColor: true
结论
获得正确的颜色(定义、获取、比较、转换...)并不是那么容易。试图在保持简单的同时指出重要的事情,但你现在应该能够做到。不要忘记正确创建颜色、转换为合适的颜色 space、选择适合您应用需求的公差等。
这是 public GitHub Gist,其中包含 Objective-C 和 Swift 视图控制器实现。
附录
不需要颜色 space 转换,因为 [UIColor getRed:green:blue:alpha:]
文档说:
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.
如果您使用此功能,应该为您转换颜色分量值。但我在这个答案中保留了转换代码,以演示幕后发生的事情。